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; }