diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.css b/client/coral-embed-stream/src/tabs/stream/components/Comment.css
index f5bead3b8..15d42bef0 100644
--- a/client/coral-embed-stream/src/tabs/stream/components/Comment.css
+++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.css
@@ -119,6 +119,8 @@
.commentInfoBar {
margin-left: auto;
+ flex: 1;
+ text-align: right;
}
@keyframes enter {
@@ -131,9 +133,6 @@
animation: enter 1000ms;
}
-.commentContainer {
-}
-
.commentAvatar {
max-width: 60px;
}
@@ -152,9 +151,32 @@
}
.header {
- display: flex;
- align-items: center;
margin: 10px 0;
+ display: flex;
+}
+
+.headerContainer {
+ flex: 1;
+ flex-direction: column;
+ align-items: flex-start;
+
+ @media (min-width: 480px) {
+ display: flex;
+ align-items: center;
+ flex-direction: row;
+ }
+}
+
+.tagsContainer {
+ display: flex;
+}
+
+.tagsContainer > * {
+ margin: 3px;
+
+ &:first-child {
+ margin-left: 0px;
+ }
}
.content {
diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.js b/client/coral-embed-stream/src/tabs/stream/components/Comment.js
index 3da59afdc..ba14e3c7f 100644
--- a/client/coral-embed-stream/src/tabs/stream/components/Comment.js
+++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.js
@@ -452,39 +452,43 @@ export default class Comment extends React.Component {
-
-
- {isStaff(comment.tags) ?
Staff : null}
-
-
-
-
+
- {
- (comment.editing && comment.editing.edited)
- ?
({t('comment.edited')})
- : null
- }
-
+
+
+ {isStaff(comment.tags) ? Staff : null}
+
+
+
+
+
+
+ {
+ (comment.editing && comment.editing.edited)
+ ? ({t('comment.edited')})
+ : null
+ }
+
+
{
+ const {
+ loaders: {
+ Comments,
+ Users,
+ },
+ } = ctx;
+
+ if (item_type === 'COMMENTS') {
+ const comment = await Comments.get.load(item_id);
+ if (!comment) {
+ throw errors.ErrNotFound;
+ }
+
+ return comment;
+ } else if (item_type === 'USERS') {
+ const user = await Users.getByID.load(item_id);
+ if (!user) {
+ throw errors.ErrNotFound;
+ }
+
+ return user;
+ }
+};
/**
* Creates an action on a item. If the item is a user flag, sets the user's status to
* pending.
- * @param {Object} user the user performing the request
- * @param {String} item_id id of the item to add the action to
- * @param {String} item_type type of the item
- * @param {String} action_type type of the action
- * @return {Promise} resolves to the action created
+ *
+ * @param {Object} ctx the graphql context for the request
+ * @param {Object} action the action being created
+ * @return {Promise} resolves to the action created
*/
-const createAction = async ({user = {}, pubsub, loaders: {Comments}}, {item_id, item_type, action_type, group_id, metadata = {}}) => {
+const createAction = async (ctx, {item_id, item_type, action_type, group_id, metadata = {}}) => {
+ const {
+ user = {},
+ pubsub,
+ connectors: {
+ services: {
+ Actions,
+ },
+ },
+ } = ctx;
- let comment;
- if (item_type === 'COMMENTS') {
- comment = await Comments.get.load(item_id);
- if (!comment) {
- throw new Error('Comment not found');
+ // Gets the item referenced by the action.
+ const item = await getActionItem(ctx, {item_id, item_type});
+
+ // If we are ignoring flags against staff, ensure that the target isn't a
+ // staff member.
+ if (IGNORE_FLAGS_AGAINST_STAFF) {
+ if (action_type === 'FLAG') {
+
+ // If the item is a user, and this is a flag. Check to see if they are
+ // staff, if they are, don't permit the flag.
+ if (item_type === 'USERS' && item.isStaff()) {
+ return null;
+ }
}
}
- let action = await ActionsService.create({
+ // Create the action itself.
+ let action = await Actions.create({
item_id,
item_type,
user_id: user.id,
@@ -31,17 +83,17 @@ const createAction = async ({user = {}, pubsub, loaders: {Comments}}, {item_id,
metadata
});
- if (item_type === 'USERS' && action_type === 'FLAG') {
+ if (action_type === 'FLAG') {
+ if (item_type === 'USERS') {
- // Set the user as pending if it was a user flag and user has no Admin, Staff or Moderation roles
- let user = await UsersService.findById(item_id);
- if(!user.isStaff()){
+ // Set the user's status as pending, as we need to review it.
await UsersService.setStatus(item_id, 'PENDING');
- }
- }
+ } else if (item_type === 'COMMENTS') {
- if (comment) {
- pubsub.publish('commentFlagged', comment);
+ // The item is a comment, and this is a flag. Push that the comment was
+ // flagged, don't wait for it to finish.
+ pubsub.publish('commentFlagged', item);
+ }
}
return action;
@@ -53,16 +105,25 @@ const createAction = async ({user = {}, pubsub, loaders: {Comments}}, {item_id,
* @param {String} id the id of the action to delete
* @return {Promise} resolves to the deleted action, or null if not found.
*/
-const deleteAction = ({user}, {id}) => {
- return ActionsService.delete({id, user_id: user.id});
+const deleteAction = (ctx, {id}) => {
+ const {
+ user,
+ connectors: {
+ services: {
+ Actions,
+ },
+ },
+ } = ctx;
+
+ return Actions.delete({id, user_id: user.id});
};
-module.exports = (context) => {
- if (context.user && context.user.can(CREATE_ACTION, DELETE_ACTION)) {
+module.exports = (ctx) => {
+ if (ctx.user && ctx.user.can(CREATE_ACTION, DELETE_ACTION)) {
return {
Action: {
- create: (action) => createAction(context, action),
- delete: (action) => deleteAction(context, action)
+ create: (action) => createAction(ctx, action),
+ delete: (action) => deleteAction(ctx, action)
}
};
}
diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js
index de3345257..bd8ab18fd 100644
--- a/graph/mutators/comment.js
+++ b/graph/mutators/comment.js
@@ -19,7 +19,8 @@ const {
} = require('../../perms/constants');
const {
- DISABLE_AUTOFLAG_SUSPECT_WORDS
+ DISABLE_AUTOFLAG_SUSPECT_WORDS,
+ IGNORE_FLAGS_AGAINST_STAFF,
} = require('../../config');
const debug = require('debug')('talk:graph:mutators:tags');
@@ -292,7 +293,7 @@ const moderationPhases = [
}
},
- // This phase checks to see if the comment's length exeeds maximum.
+ // This phase checks to see if the comment's length exceeds maximum.
(context, comment, {assetSettings: {charCountEnable, charCount}}) => {
// Reject if the comment is too long
@@ -313,6 +314,15 @@ const moderationPhases = [
}
},
+ // If a given user is a staff member, always approve their comment.
+ (context) => {
+ if (IGNORE_FLAGS_AGAINST_STAFF && context.user && context.user.isStaff()) {
+ return {
+ status: 'ACCEPTED',
+ };
+ }
+ },
+
// This phase checks the comment if it has any links in it if the check is
// enabled.
(context, comment, {assetSettings: {premodLinksEnable}}) => {
@@ -362,7 +372,7 @@ const moderationPhases = [
}
},
- // This phase checks to see if the comment was already perscribed a status.
+ // This phase checks to see if the comment was already prescribed a status.
(context, comment) => {
// If the status was already defined, don't redefine it. It's only defined
diff --git a/plugin-api/beta/server/getReactionConfig.js b/plugin-api/beta/server/getReactionConfig.js
index 481990c16..2fffb4d1c 100644
--- a/plugin-api/beta/server/getReactionConfig.js
+++ b/plugin-api/beta/server/getReactionConfig.js
@@ -157,10 +157,22 @@ function getReactionConfig(reaction) {
RootMutation: {
[`create${Reaction}Action`]: async (_, {input: {item_id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => {
const comment = await Comments.get.load(item_id);
+ if (!comment) {
+ throw errors.ErrNotFound;
+ }
- let action;
try {
- action = await Action.create({item_id, item_type: 'COMMENTS', action_type: REACTION});
+ const action = await Action.create({item_id, item_type: 'COMMENTS', action_type: REACTION});
+
+ if (pubsub) {
+
+ // The comment is needed to allow better filtering e.g. by asset_id.
+ pubsub.publish(`${reaction}ActionCreated`, {action, comment});
+ }
+
+ return {
+ [reaction]: action,
+ };
} catch (err) {
if (err instanceof errors.ErrAlreadyExists) {
return err.metadata.existing;
@@ -168,16 +180,6 @@ function getReactionConfig(reaction) {
throw err;
}
-
- if (pubsub) {
-
- // The comment is needed to allow better filtering e.g. by asset_id.
- pubsub.publish(`${reaction}ActionCreated`, {action, comment});
- }
-
- return {
- [reaction]: action,
- };
},
[`delete${Reaction}Action`]: async (_, {input: {id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => {
const action = await Action.delete({id});
diff --git a/plugins/talk-plugin-subscriber/client/components/SubscriberBadge.css b/plugins/talk-plugin-subscriber/client/components/SubscriberBadge.css
index 42edfd8ff..e6c8d9161 100644
--- a/plugins/talk-plugin-subscriber/client/components/SubscriberBadge.css
+++ b/plugins/talk-plugin-subscriber/client/components/SubscriberBadge.css
@@ -2,7 +2,6 @@
background-color: #616161;
color: white;
display: inline-block;
- margin: 0px 5px;
padding: 5px 5px;
border-radius: 2px;
font-size: 12px;
diff --git a/postcss.config.js b/postcss.config.js
index 79fae36cb..e76e674d2 100644
--- a/postcss.config.js
+++ b/postcss.config.js
@@ -1,7 +1,7 @@
module.exports = {
plugins: [
- require('postcss-smart-import')({ /* ...options */ }),
- require('precss')({ /* ...options */ }),
- require('autoprefixer')({ /* ...options */ })
- ]
+ require('postcss-smart-import'),
+ require('precss'),
+ require('autoprefixer'),
+ ],
};
diff --git a/services/users.js b/services/users.js
index e0f7d1599..fbaf89609 100644
--- a/services/users.js
+++ b/services/users.js
@@ -395,18 +395,34 @@ module.exports = class UsersService {
throw new Error(`status ${status} is not supported`);
}
- // TODO: current updating status behavior is weird.
- // once a user has been `APPROVED` its status cannot be
- // changed anymore.
- const user = await UserModel.findOneAndUpdate({
- id,
- status: {
- $ne: 'APPROVED'
- }
- }, {
+ // Compose the query.
+ const query = {id};
+
+ // Insert extra validations into the query.
+ switch (status) {
+ case 'ACTIVE':
+ case 'BANNED':
+ case 'APPROVED':
+
+ // A user cannot become change their status from what it is already.
+ query.status = {
+ $ne: status,
+ };
+ break;
+ case 'PENDING':
+
+ // A user cannot become pending if they are already approved, pending, or
+ // banned
+ query.status = {
+ $nin: [status, 'APPROVED', 'BANNED'],
+ };
+ break;
+ }
+
+ const user = await UserModel.findOneAndUpdate(query, {
$set: {
- status
- }
+ status,
+ },
}, {
new: true,
});
@@ -426,8 +442,8 @@ module.exports = class UsersService {
};
await MailerService.sendSimple(options);
}
-
}
+
return user;
}