From 2f94674b78dce39565a5813d0f44cdd508cbceae Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 8 May 2018 11:08:46 -0300 Subject: [PATCH 1/7] Adding CommentDeletedTombstone --- .../src/components/CommentAnimatedEdit.js | 2 +- .../components/CommentDeletedTombstone.css | 5 + .../src/components/CommentDeletedTombstone.js | 8 ++ .../src/components/UserDetailComment.js | 87 +++++++------ .../routes/Moderation/components/Comment.js | 83 ++++++------ .../components/ChangeUsernameDialog.css | 84 ++++++++++++ .../client/components/ChangeUsernameDialog.js | 120 ++++++++++++++++++ 7 files changed, 308 insertions(+), 81 deletions(-) create mode 100644 client/coral-admin/src/components/CommentDeletedTombstone.css create mode 100644 client/coral-admin/src/components/CommentDeletedTombstone.js create mode 100644 plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.css create mode 100644 plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.js diff --git a/client/coral-admin/src/components/CommentAnimatedEdit.js b/client/coral-admin/src/components/CommentAnimatedEdit.js index 12280b204..f43cb9c89 100644 --- a/client/coral-admin/src/components/CommentAnimatedEdit.js +++ b/client/coral-admin/src/components/CommentAnimatedEdit.js @@ -29,7 +29,7 @@ const CommentAnimatedEdit = ({ children, body }) => { CommentAnimatedEdit.propTypes = { children: PropTypes.node, - body: PropTypes.string, + body: PropTypes.string.isRequired, }; export default CommentAnimatedEdit; diff --git a/client/coral-admin/src/components/CommentDeletedTombstone.css b/client/coral-admin/src/components/CommentDeletedTombstone.css new file mode 100644 index 000000000..81f5ddf7a --- /dev/null +++ b/client/coral-admin/src/components/CommentDeletedTombstone.css @@ -0,0 +1,5 @@ +.tombstone { + background-color: #f0f0f0; + padding: 1em; + color: #1a212f; +} \ No newline at end of file diff --git a/client/coral-admin/src/components/CommentDeletedTombstone.js b/client/coral-admin/src/components/CommentDeletedTombstone.js new file mode 100644 index 000000000..db9a609b1 --- /dev/null +++ b/client/coral-admin/src/components/CommentDeletedTombstone.js @@ -0,0 +1,8 @@ +import React from 'react'; +import styles from './CommentDeletedTombstone.css'; + +const CommentDeletedTombstone = () => ( +
The comment was deleted.
+); + +export default CommentDeletedTombstone; diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index f2ff2e932..f7a500203 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -12,6 +12,7 @@ import CommentAnimatedEdit from './CommentAnimatedEdit'; import CommentLabels from '../containers/CommentLabels'; import ApproveButton from './ApproveButton'; import RejectButton from 'coral-admin/src/components/RejectButton'; +import CommentDeletedTombstone from './CommentDeletedTombstone'; import t, { timeago } from 'coral-framework/services/i18n'; @@ -83,49 +84,53 @@ class UserDetailComment extends React.Component { } - -
-
- - - {t('comment.view_context')} - -
-
- - - {/* TODO: translate string */} - Contains Link - - -
- - +
+ +
+ + + {/* TODO: translate string */} + Contains Link + + +
+ + +
-
- + + ) : ( + + )}
@@ -152,7 +157,7 @@ UserDetailComment.propTypes = { comment: PropTypes.shape({ id: PropTypes.string.isRequired, status: PropTypes.string.isRequired, - body: PropTypes.string.isRequired, + body: PropTypes.string, actions: PropTypes.array, created_at: PropTypes.string.isRequired, asset: PropTypes.shape({ diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index 28dcf8ca8..137bd9381 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -13,6 +13,7 @@ import IfHasLink from 'coral-admin/src/components/IfHasLink'; import cn from 'classnames'; import ApproveButton from 'coral-admin/src/components/ApproveButton'; import RejectButton from 'coral-admin/src/components/RejectButton'; +import CommentDeletedTombstone from '../../../components/CommentDeletedTombstone'; import t, { timeago } from 'coral-framework/services/i18n'; @@ -133,48 +134,52 @@ class Comment extends React.Component { )}
- -
-
- -
- - {t('comment.view_context')} - + {comment.body ? ( + + -
- - - {/* TODO: translate string */} - Contains Link - - -
- - +
+ + + {/* TODO: translate string */} + Contains Link + + +
+ + +
+
-
-
-
+ + ) : ( + + )}
{ + this.setState({ + showError: true, + }); + }; + + confirmChanges = async () => { + if (this.formHasError()) { + this.showError(); + return; + } + + if (!this.props.canUsernameBeUpdated) { + this.props.notify( + 'error', + t('talk-plugin-local-auth.change_username.change_username_attempt') + ); + return; + } + + await this.props.saveChanges(); + this.props.closeDialog(); + }; + + formHasError = () => + this.props.formData.confirmNewUsername !== this.props.formData.newUsername; + + render() { + return ( + + + × + +

+ {t('talk-plugin-local-auth.change_username.confirm_username_change')} +

+
+

+ {t('talk-plugin-local-auth.change_username.description')} +

+
+ + {t('talk-plugin-local-auth.change_username.old_username')}:{' '} + {this.props.username} + + + {t('talk-plugin-local-auth.change_username.new_username')}:{' '} + {this.props.formData.newUsername} + +
+
+ + + {t('talk-plugin-local-auth.change_username.bottom_note')} + + +
+
+ + +
+
+
+ ); + } +} + +ChangeUsernameDialog.propTypes = { + saveChanges: PropTypes.func, + closeDialog: PropTypes.func, + showDialog: PropTypes.bool, + onChange: PropTypes.func, + username: PropTypes.string, + formData: PropTypes.object, + canUsernameBeUpdated: PropTypes.bool.isRequired, + notify: PropTypes.func.isRequired, +}; + +export default ChangeUsernameDialog; From e9c18faf32bd9641f30bc42d3801d272455bf2de Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 8 May 2018 11:30:33 -0300 Subject: [PATCH 2/7] Deleted Comment display in Queues --- .../src/components/CommentDeletedTombstone.js | 3 +- .../src/components/UserDetailComment.js | 97 ++++++++-------- .../routes/Moderation/components/Comment.css | 4 + .../routes/Moderation/components/Comment.js | 105 ++++++++++-------- 4 files changed, 120 insertions(+), 89 deletions(-) diff --git a/client/coral-admin/src/components/CommentDeletedTombstone.js b/client/coral-admin/src/components/CommentDeletedTombstone.js index db9a609b1..c04e97b88 100644 --- a/client/coral-admin/src/components/CommentDeletedTombstone.js +++ b/client/coral-admin/src/components/CommentDeletedTombstone.js @@ -1,8 +1,9 @@ import React from 'react'; import styles from './CommentDeletedTombstone.css'; +import t from 'coral-framework/services/i18n'; const CommentDeletedTombstone = () => ( -
The comment was deleted.
+
{t('framework.comment_is_deleted')}
); export default CommentDeletedTombstone; diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index f7a500203..41fb87b28 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -44,6 +44,19 @@ class UserDetailComment extends React.Component { body: comment.body, }; + if (!comment.body) { + return ( +
  • + +
  • + ); + } + return (
  • }
  • - {comment.body ? ( - -
    -
    - +
    + +
    + + + {/* TODO: translate string */} + Contains Link + + + -
    - - - {/* TODO: translate string */} - Contains Link - - -
    - - -
    - - ) : ( - - )} +
    +
    diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.css b/client/coral-admin/src/routes/Moderation/components/Comment.css index 2dc03a503..0f8f91a97 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.css +++ b/client/coral-admin/src/routes/Moderation/components/Comment.css @@ -85,6 +85,10 @@ font-weight: 300; } +.deleted { + background-color: #f0f0f0; +} + .moderateArticle { font-size: 14px; margin: 10px 0; diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index 137bd9381..97cdf9443 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -76,6 +76,27 @@ class Comment extends React.Component { asset: comment.asset, }; + if (!comment.body) { + return ( +
  • + +
  • + ); + } + return (
  • )}
  • - {comment.body ? ( - -
    - - -
    - - - {/* TODO: translate string */} - Contains Link - - -
    - - -
    - + +
    + - - ) : ( - - )} + +
    + + + {/* TODO: translate string */} + Contains Link + + +
    + + +
    + +
    +
    +
    Date: Tue, 8 May 2018 11:33:39 -0300 Subject: [PATCH 3/7] removing extra --- .../components/ChangeUsernameDialog.css | 84 ------------ .../client/components/ChangeUsernameDialog.js | 120 ------------------ 2 files changed, 204 deletions(-) delete mode 100644 plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.css delete mode 100644 plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.js diff --git a/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.css b/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.css deleted file mode 100644 index af681d596..000000000 --- a/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.css +++ /dev/null @@ -1,84 +0,0 @@ -.dialog { - border: none; - box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); - width: 320px; - top: 10px; - font-family: Helvetica, 'Helvetica Neue', Verdana, sans-serif; - font-size: 14px; - border-radius: 4px; - padding: 12px 20px; -} - -.close { - font-size: 20px; - line-height: 14px; - top: 10px; - right: 10px; - position: absolute; - display: block; - font-weight: bold; - color: #363636; - cursor: pointer; - - &:hover { - color: #6b6b6b; - } -} - -.title { - font-size: 1.3em; - margin-bottom: 8px; -} - -.description { - font-size: 1em; - line-height: 20px; - margin: 0; -} - -.item { - display: block; - color: #4C4C4D; - font-size: 1em; - margin-bottom: 2px; -} - -.bottomNote { - font-size: 0.9em; - line-height: 20px; - padding-top: 10px; - display: block; -} - -.bottomActions { - text-align: right; -} - -.usernamesChange { - margin: 18px 0; -} - -.cancel { - border: 1px solid #787d80; - background-color: transparent; - height: 30px; - font-size: 0.9em; - line-height: normal; - - &:hover { - background-color: #eaeaea; - } -} - -.confirmChanges { - background-color: #3498DB; - border-color: #3498DB; - color: white; - height: 30px; - font-size: 0.9em; - - &:hover { - background-color: #3ba3ec; - color: white; - } -} \ No newline at end of file diff --git a/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.js b/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.js deleted file mode 100644 index 097168b06..000000000 --- a/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.js +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cn from 'classnames'; -import styles from './ChangeUsernameDialog.css'; -import InputField from './InputField'; -import { Button, Dialog } from 'plugin-api/beta/client/components/ui'; -import { t } from 'plugin-api/beta/client/services'; - -class ChangeUsernameDialog extends React.Component { - state = { - showError: false, - }; - - showError = () => { - this.setState({ - showError: true, - }); - }; - - confirmChanges = async () => { - if (this.formHasError()) { - this.showError(); - return; - } - - if (!this.props.canUsernameBeUpdated) { - this.props.notify( - 'error', - t('talk-plugin-local-auth.change_username.change_username_attempt') - ); - return; - } - - await this.props.saveChanges(); - this.props.closeDialog(); - }; - - formHasError = () => - this.props.formData.confirmNewUsername !== this.props.formData.newUsername; - - render() { - return ( - - - × - -

    - {t('talk-plugin-local-auth.change_username.confirm_username_change')} -

    -
    -

    - {t('talk-plugin-local-auth.change_username.description')} -

    -
    - - {t('talk-plugin-local-auth.change_username.old_username')}:{' '} - {this.props.username} - - - {t('talk-plugin-local-auth.change_username.new_username')}:{' '} - {this.props.formData.newUsername} - -
    -
    - - - {t('talk-plugin-local-auth.change_username.bottom_note')} - - -
    -
    - - -
    -
    -
    - ); - } -} - -ChangeUsernameDialog.propTypes = { - saveChanges: PropTypes.func, - closeDialog: PropTypes.func, - showDialog: PropTypes.bool, - onChange: PropTypes.func, - username: PropTypes.string, - formData: PropTypes.object, - canUsernameBeUpdated: PropTypes.bool.isRequired, - notify: PropTypes.func.isRequired, -}; - -export default ChangeUsernameDialog; From 74dbfda63368538ed9b82131ba0af058d67c0fd6 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 11:53:49 -0600 Subject: [PATCH 4/7] Exclude deleted comments --- .../Moderation/containers/Moderation.js | 2 ++ graph/loaders/comments.js | 34 +++++++++++++------ graph/typeDefs.graphql | 11 +++++- services/comments.js | 12 +++++-- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index f35255732..c908404cb 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -432,6 +432,7 @@ const withModQueueQuery = withQuery( ${Object.keys(queueConfig).map( queue => ` ${queue}: comments(query: { + excludeDeleted: true, statuses: ${ queueConfig[queue].statuses ? `[${queueConfig[queue].statuses.join(', ')}],` @@ -458,6 +459,7 @@ const withModQueueQuery = withQuery( ${Object.keys(queueConfig).map( queue => ` ${queue}Count: commentCount(query: { + excludeDeleted: true, statuses: ${ queueConfig[queue].statuses ? `[${queueConfig[queue].statuses.join(', ')}],` diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index 37d40a219..b42a636bc 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -94,6 +94,7 @@ const getCommentCountByQuery = (ctx, options) => { author_id, tags, action_type, + excludeDeleted, } = options; // If user queries for statuses other than NONE and/or ACCEPTED statuses, it needs @@ -120,6 +121,12 @@ const getCommentCountByQuery = (ctx, options) => { query.merge({ author_id }); } + if (excludeDeleted) { + // The null query matches documents that either contain the `deleted_at` + // field whose value is null or that do not contain the `deleted_at` field. + query.merge({ deleted_at: null }); + } + if (ctx.user != null && ctx.user.can(SEARCH_OTHERS_COMMENTS) && action_type) { query.merge({ [`action_counts.${sc(action_type.toLowerCase())}`]: { @@ -328,11 +335,12 @@ const getCommentsByQuery = async ( sortOrder, sortBy, excludeIgnored, + excludeDeleted, tags, action_type, } ) => { - let comments = CommentModel.find(); + const query = CommentModel.find(); // Enforce that the limit must be gte 0 if this option is not true. if (!ALLOW_NO_LIMIT_QUERIES && limit < 0) { @@ -350,11 +358,17 @@ const getCommentsByQuery = async ( } if (statuses) { - comments = comments.where({ status: { $in: statuses } }); + query.merge({ status: { $in: statuses } }); + } + + if (excludeDeleted) { + // The null query matches documents that either contain the `deleted_at` + // field whose value is null or that do not contain the `deleted_at` field. + query.merge({ deleted_at: null }); } if (ctx.user != null && ctx.user.can(SEARCH_OTHERS_COMMENTS) && action_type) { - comments = comments.where({ + query.merge({ [`action_counts.${sc(action_type.toLowerCase())}`]: { $gt: 0, }, @@ -362,7 +376,7 @@ const getCommentsByQuery = async ( } if (ids) { - comments = comments.find({ + query.merge({ id: { $in: ids, }, @@ -370,7 +384,7 @@ const getCommentsByQuery = async ( } if (tags) { - comments = comments.find({ + query.merge({ 'tags.tag.name': { $in: tags, }, @@ -383,17 +397,17 @@ const getCommentsByQuery = async ( (ctx.user.can(SEARCH_OTHERS_COMMENTS) || ctx.user.id === author_id) && author_id != null ) { - comments = comments.where({ author_id }); + query.merge({ author_id }); } if (asset_id) { - comments = comments.where({ asset_id }); + query.merge({ asset_id }); } // We perform the undefined check because, null, is a valid state for the // search to be with, which indicates that it is at depth 0. if (parent_id !== undefined) { - comments = comments.where({ parent_id }); + query.merge({ parent_id }); } if ( @@ -402,12 +416,12 @@ const getCommentsByQuery = async ( ctx.user.ignoresUsers && ctx.user.ignoresUsers.length > 0 ) { - comments = comments.where({ + query.merge({ author_id: { $nin: ctx.user.ignoresUsers }, }); } - return executeWithSort(ctx, comments, { cursor, sortOrder, sortBy, limit }); + return executeWithSort(ctx, query, { cursor, sortOrder, sortBy, limit }); }; /** diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index b5c193e3d..03c97ce58 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -418,6 +418,9 @@ input CommentsQuery { # Exclude comments ignored by the requesting user excludeIgnored: Boolean + + # excludeDeleted when true will exclude deleted comments from the response. + excludeDeleted: Boolean = false } input RepliesQuery { @@ -434,6 +437,9 @@ input RepliesQuery { # Exclude comments ignored by the requesting user excludeIgnored: Boolean + + # excludeDeleted when true will exclude deleted comments from the response. + excludeDeleted: Boolean = false } # CommentCountQuery allows the ability to query comment counts by specific @@ -463,6 +469,9 @@ input CommentCountQuery { # Filter by a specific tag name. tags: [String!] + + # excludeDeleted when true will exclude deleted comments from the count. + excludeDeleted: Boolean = false } # UserCountQuery allows the ability to query user counts by specific @@ -519,7 +528,7 @@ type Comment { replies(query: RepliesQuery = {}): CommentConnection! # replyCount is the number of replies with a depth of 1. Only direct replies - # to this comment are counted. + # to this comment are counted. Deleted comments are included in this count. replyCount: Int # Actions completed on the parent. Requires the `ADMIN` role. diff --git a/services/comments.js b/services/comments.js index 4b436b5f4..19b611089 100644 --- a/services/comments.js +++ b/services/comments.js @@ -39,9 +39,12 @@ module.exports = { const created_at = new Date(); // Check to see if we are replying to a comment, and if that comment is - // visible. + // visible and that it's not deleted. if (parent_id !== null) { - const parent = await CommentModel.findOne({ id: parent_id }); + const parent = await CommentModel.findOne({ + id: parent_id, + deleted_at: null, + }); if (parent === null || !parent.visible) { throw new ErrParentDoesNotVisible(); } @@ -94,6 +97,7 @@ module.exports = { status: { $in: EDITABLE_STATUSES, }, + deleted_at: null, }; // Establish the edit window (if it exists) and add the condition to the @@ -186,8 +190,10 @@ module.exports = { */ pushStatus: async (id, status, assigned_by = null) => { const created_at = new Date(); + + // Update the comment unless the comment was deleted. const originalComment = await CommentModel.findOneAndUpdate( - { id }, + { id, deleted_at: null }, { $push: { status_history: { From e14af46c68c9a7fcad6fcaa58a4ea4aaa651db85 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 12:02:04 -0600 Subject: [PATCH 5/7] prevent actions against deleted comments --- graph/mutators/action.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/graph/mutators/action.js b/graph/mutators/action.js index d759449b0..5557e1a9b 100644 --- a/graph/mutators/action.js +++ b/graph/mutators/action.js @@ -14,8 +14,15 @@ const getActionItem = async (ctx, { item_id, item_type }) => { const { loaders: { Comments, Users } } = ctx; switch (item_type) { - case 'COMMENTS': - return Comments.get.load(item_id); + case 'COMMENTS': { + // Get a comment by ID, unless the comment is deleted, then return null. + const comment = await Comments.get.load(item_id); + if (comment.deleted_at) { + return null; + } + + return comment; + } case 'USERS': return Users.getByID.load(item_id); default: From a1edbcf974e10986ec15613563b7f00080f66143 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 14:01:04 -0600 Subject: [PATCH 6/7] disallow adding a tag to a deleted comment --- services/tags.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/tags.js b/services/tags.js index b4e70cb29..18cf5869f 100644 --- a/services/tags.js +++ b/services/tags.js @@ -163,6 +163,11 @@ class TagsService { }, }; + if (item_type === 'COMMENT') { + // Don't allow adding tags to deleted comments. + query.deleted_at = null; + } + // If ownership verification is required, ensure that the person that is // assigning the tag is the same person that owns the comment. if (ownershipCheck) { From 17a0297911754e2049453aa9a7ad4878f4252094 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 14:02:47 -0600 Subject: [PATCH 7/7] fixed bug.. --- services/tags.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/services/tags.js b/services/tags.js index 18cf5869f..49d271c0b 100644 --- a/services/tags.js +++ b/services/tags.js @@ -13,6 +13,10 @@ const updateModel = async (item_type, query, update) => { switch (item_type) { case 'COMMENTS': Model = Comment; + + // Don't allow adding tags to deleted comments. + query.deleted_at = null; + break; case 'ASSETS': Model = Asset; @@ -163,11 +167,6 @@ class TagsService { }, }; - if (item_type === 'COMMENT') { - // Don't allow adding tags to deleted comments. - query.deleted_at = null; - } - // If ownership verification is required, ensure that the person that is // assigning the tag is the same person that owns the comment. if (ownershipCheck) {