diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.css b/client/coral-admin/src/routes/Moderation/components/Comment.css index 708761b95..119688b73 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.css +++ b/client/coral-admin/src/routes/Moderation/components/Comment.css @@ -27,6 +27,10 @@ } } +.dangling { + background-color: #efefef; +} + .container { padding: 0 14px; } diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index 0856e4657..293071cec 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -82,6 +82,7 @@ class Comment extends React.Component { currentUserId, currentAsset, clearHeightCache, + dangling, } = this.props; const selectionStateCSS = selected ? 'mdl-shadow--16dp' : 'mdl-shadow--2dp'; @@ -90,7 +91,7 @@ class Comment extends React.Component { return (
  • props.commentBelongToQueue(props.activeTab, comment)).length; } async selectDown() { const view = this.state.view; const index = view.findIndex(({id}) => id === this.props.selectedCommentId); - if (index === view.length - 1 && this.props.comments.length !== this.props.commentCount) { + if (index === view.length - 1 && this.getCommentCountWithoutDagling() !== this.props.commentCount) { await this.props.loadMore(); this.selectDown(); return; @@ -139,20 +145,20 @@ class ModerationQueue extends React.Component { } componentDidUpdate (prev) { - const {comments, commentCount} = this.props; + const {commentCount, selectedCommentId} = this.props; // if the user just moderated the last (visible) comment // AND there are more comments available on the server, // go ahead and load more comments - if (prev.comments.length > 0 && comments.length === 0 && commentCount > 0) { + if (prev.comments.length > 0 && this.getCommentCountWithoutDagling() === 0 && commentCount > 0) { this.props.loadMore(); } // Scroll to selected comment. - if (prev.selectedCommentId !== this.props.selectedCommentId && this.listRef) { + if (prev.selectedCommentId !== selectedCommentId && this.listRef) { const view = this.state.view; - const index = view.findIndex(({id}) => id === this.props.selectedCommentId); + const index = view.findIndex(({id}) => id === selectedCommentId); this.listRef.scrollToRow(index); } @@ -244,7 +250,7 @@ class ModerationQueue extends React.Component { const view = this.state.view; const rowCount = view.length + 1; if (index === rowCount - 1) { - const hasMore = this.props.comments.length < this.props.commentCount; + const hasMore = this.getCommentCountWithoutDagling() < this.props.commentCount; return ( { + if (!this.props.data.loading) { + this.props.data.updateQuery((query) => { + return cleanUpQueue(query, queue, this.props.queueConfig); + }); + } + } + acceptComment = ({commentId}) => { return this.props.setCommentStatus({commentId, status: 'ACCEPTED'}); } @@ -177,6 +185,10 @@ class ModerationContainer extends Component { return this.props.setCommentStatus({commentId, status: 'REJECTED'}); } + commentBelongToQueue = (queue, comment) => { + return commentBelongToQueue(queue, comment, this.props.queueConfig); + } + loadMore = (tab) => { const variables = { limit: 20, @@ -248,6 +260,8 @@ class ModerationContainer extends Component { queueConfig={currentQueueConfig} handleCommentChange={this.handleCommentChange} selectedCommentId={this.props.selectedCommentId} + commentBelongToQueue={this.commentBelongToQueue} + cleanUpQueue={this.cleanUpQueue} />; } } @@ -389,6 +403,9 @@ const withModQueueQuery = withQuery(({queueConfig}) => gql` organizationName moderation } + me { + id + } ...${getDefinitionName(Comment.fragments.root)} } ${Comment.fragments.root} diff --git a/client/coral-admin/src/routes/Moderation/graphql.js b/client/coral-admin/src/routes/Moderation/graphql.js index e99bb4a7e..ec6bc4f88 100644 --- a/client/coral-admin/src/routes/Moderation/graphql.js +++ b/client/coral-admin/src/routes/Moderation/graphql.js @@ -16,16 +16,21 @@ function queueHasComment(root, queue, id) { return root[queue].nodes.find((c) => c.id === id); } -function removeCommentFromQueue(root, queue, id) { +function removeCommentFromQueue(root, queue, id, dangling = false) { if (!queueHasComment(root, queue, id)) { return root; } - return update(root, { + const changes = { [`${queue}Count`]: {$set: root[`${queue}Count`] - 1}, - [queue]: { + }; + + if (!dangling) { + changes[queue] = { nodes: {$apply: (nodes) => nodes.filter((c) => c.id !== id)}, - }, - }); + }; + } + + return update(root, changes); } function shouldCommentBeAdded(root, queue, comment, sortOrder) { @@ -70,24 +75,28 @@ export function sortComments(nodes, sortOrder) { function getCommentQueues(comment, queueConfig) { const queues = []; Object.keys(queueConfig).forEach((key) => { - const {action_type, statuses, tags} = queueConfig[key]; - let addToQueues = true; - if (statuses && statuses.indexOf(comment.status) === -1) { - addToQueues = false; - } - if (tags && (!comment.tags || !comment.tags.some((tagLink) => tags.indexOf(tagLink.tag.name) >= 0))) { - addToQueues = false; - } - if (action_type && (!comment.actions || !comment.actions.some((a) => a.__typename.toLowerCase() === `${action_type.toLowerCase()}action`))) { - addToQueues = false; - } - if (addToQueues) { + if (commentBelongToQueue(key, comment, queueConfig)) { queues.push(key); } }); return queues; } +export function commentBelongToQueue(queue, comment, queueConfig) { + const {action_type, statuses, tags} = queueConfig[queue]; + let belong = true; + if (statuses && statuses.indexOf(comment.status) === -1) { + belong = false; + } + if (tags && (!comment.tags || !comment.tags.some((tagLink) => tags.indexOf(tagLink.tag.name) >= 0))) { + belong = false; + } + if (action_type && (!comment.actions || !comment.actions.some((a) => a.__typename.toLowerCase() === `${action_type.toLowerCase()}action`))) { + belong = false; + } + return belong; +} + function isVisible(id) { return !!document.getElementById(`comment_${id}`); } @@ -96,6 +105,37 @@ function isEndOfListVisible(root, queue) { return root[queue].nodes.length === 0 || !!document.getElementById('end-of-comment-list'); } +function applyCommentChanges(root, comment, queueConfig) { + const queues = Object.keys(queueConfig); + for (let i = 0; i < queues.length; i++) { + const queue = queues[i]; + const index = root[queue].nodes.findIndex(({id}) => id === comment.id); + if (index > -1) { + return update(root, { + [queue]: { + nodes: { + [index]: {$merge: comment}, + }, + }, + }); + } + } + return root; +} + +export function cleanUpQueue(root, queue, queueConfig) { + return update(root, { + [queue]: { + nodes: { + $apply: (nodes) => + sortComments( + nodes.filter((comment) => commentBelongToQueue(queue, comment, queueConfig)) + ).slice(0, 50), + }, + }, + }); +} + /** * Assimilate comment changes into current store. * @param {Object} root current state of the store @@ -129,7 +169,8 @@ export function handleCommentChange(root, comment, sortOrder, notify, queueConfi } } } else if(queueHasComment(next, queue, comment.id)){ - next = removeCommentFromQueue(next, queue, comment.id); + const dangling = activeQueue === queue && comment.status_history[comment.status_history.length - 1].assigned_by.id !== root.me.id; + next = removeCommentFromQueue(next, queue, comment.id, dangling); if (notify && isVisible(comment.id)) { showNotificationOnce(); } @@ -138,6 +179,8 @@ export function handleCommentChange(root, comment, sortOrder, notify, queueConfi if (notify && isVisible(comment.id)) { showNotificationOnce(); } + + next = applyCommentChanges(next, comment, queueConfig); }); return next; }