diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index a31fbd313..b18f239e4 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -10,6 +10,7 @@ import FlagBox from './FlagBox'; import CommentType from './CommentType'; import ActionButton from 'coral-admin/src/components/ActionButton'; import BanUserButton from 'coral-admin/src/components/BanUserButton'; +import {getActionSummary} from 'coral-framework/utils'; const linkify = new Linkify(); @@ -20,8 +21,8 @@ const lang = new I18n(translations); const Comment = ({actions = [], comment, ...props}) => { const links = linkify.getMatches(comment.body); const linkText = links ? links.map(link => link.raw) : []; - const actionSummaries = comment.action_summaries.filter(a => a.__typename === 'FlagActionSummary'); - const flagActions = comment.actions.filter(a => a.__typename === 'FlagAction'); + const flagActionSummaries = getActionSummary('FlagActionSummary', comment); + const flagActions = comment.actions && comment.actions.filter(a => a.__typename === 'FlagAction'); return (
  • @@ -71,7 +72,11 @@ const Comment = ({actions = [], comment, ...props}) => { - {flagActions && } + { + flagActions && flagActions.length + ? + : null + }
  • ); }; diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 4a75a19cb..51402e490 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -22,11 +22,10 @@ import LoadMore from 'coral-embed-stream/src/LoadMore'; import {Slot} from 'coral-framework'; import IgnoredCommentTombstone from './IgnoredCommentTombstone'; import {TopRightMenu} from './TopRightMenu'; +import {getActionSummary, getTotalActionCount, iPerformedThisAction} from 'coral-framework/utils'; import styles from './Comment.css'; -const getActionSummary = (type, comment) => comment.action_summaries - .filter((a) => a.__typename === type)[0]; const isStaff = (tags) => !tags.every((t) => t.name !== 'STAFF') ; // hold actions links (e.g. Like, Reply) along the comment footer @@ -124,9 +123,16 @@ class Comment extends React.Component { commentIsIgnored, } = this.props; - const like = getActionSummary('LikeActionSummary', comment); - const flag = getActionSummary('FlagActionSummary', comment); - const dontagree = getActionSummary('DontAgreeActionSummary', comment); + const likeSummary = getActionSummary('LikeActionSummary', comment); + const flagSummary = getActionSummary('FlagActionSummary', comment); + const dontAgreeSummary = getActionSummary('DontAgreeActionSummary', comment); + let myFlag = null; + if (iPerformedThisAction('FlagActionSummary', comment)) { + myFlag = flagSummary.find(s => s.current_user); + } else if (iPerformedThisAction('DontAgreeActionSummary', comment)) { + myFlag = dontAgreeSummary.find(s => s.current_user); + } + let commentClass = parentId ? `reply ${styles.Reply}` : `comment ${styles.Comment}`; commentClass += comment.id === 'pending' ? ` ${styles.pendingComment}` : ''; @@ -183,8 +189,10 @@ class Comment extends React.Component {
    + {/* TODO implmement iPerformedThisAction for the like */} { + return comment.action_summaries + .filter(s => s.__typename === type) + .reduce((total, summary) => { + return total + summary.count; + }, 0); +}; -export const getActionSummary = (type, comment) => - comment.action_summaries.filter(a => a.__typename === type)[0]; +export const iPerformedThisAction = (type, comment) => { + + // if there is a current_user on any of the ActionSummary(s), the user performed this action + return comment.action_summaries + .filter(a => a.__typename === type) + .some(a => a.current_user); +}; + +export const getMyActionSummary = (type, comment) => { + return comment.action_summaries + .filter(a => a.__typename === type) + .find(a => a.current_user); +}; + + /** + * getActionSummary + * retrieves the action summaries based on the type and the comment + * array could be length > 1, as in the case of FlagActionSummary + */ + +export const getActionSummary = (type, comment) => { + return comment.action_summaries.filter(a => a.__typename === type); +}; diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js index 45110f407..054ca2fb1 100644 --- a/client/coral-plugin-flags/FlagButton.js +++ b/client/coral-plugin-flags/FlagButton.js @@ -21,15 +21,15 @@ class FlagButton extends Component { // When the "report" button is clicked expand the menu onReportClick = () => { - const {currentUser, flag, deleteAction} = this.props; + const {currentUser, deleteAction, flaggedByCurrentUser, flag} = this.props; const {localPost, localDelete} = this.state; - const flagged = (flag && flag.current_user && !localDelete) || localPost; + const localFlagged = (flaggedByCurrentUser && !localDelete) || localPost; if (!currentUser) { const offset = document.getElementById(`c_${this.props.id}`).getBoundingClientRect().top - 75; this.props.showSignInDialog(offset); return; } - if (flagged) { + if (localFlagged) { this.setState((prev) => prev.localPost ? {...prev, localPost: null, step: 0} : {...prev, localDelete: true}); deleteAction(localPost || flag.current_user.id); } else if (this.state.showMenu){ @@ -130,9 +130,9 @@ class FlagButton extends Component { } render () { - const {flag, getPopupMenu} = this.props; + const {getPopupMenu, flaggedByCurrentUser} = this.props; const {localPost, localDelete} = this.state; - const flagged = (flag && flag.current_user && !localDelete) || localPost; + const flagged = (flaggedByCurrentUser && !localDelete) || localPost; const popupMenu = getPopupMenu[this.state.step](this.state.itemType); return
    diff --git a/client/coral-plugin-likes/LikeButton.js b/client/coral-plugin-likes/LikeButton.js index 51854c57f..8f6964051 100644 --- a/client/coral-plugin-likes/LikeButton.js +++ b/client/coral-plugin-likes/LikeButton.js @@ -27,9 +27,9 @@ class LikeButton extends Component { render() { const {like, id, postLike, deleteAction, showSignInDialog, currentUser} = this.props; + let {totalLikes: count} = this.props; const {localPost, localDelete} = this.state; const liked = (like && like.current_user && !localDelete) || localPost; - let count = like ? like.count : 0; if (localPost) {count += 1;} if (localDelete) {count -= 1;} diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 0a067b49c..eb376b0de 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -31,7 +31,7 @@ type User { username: String! # Action summaries against the user. - action_summaries: [ActionSummary] + action_summaries: [ActionSummary]! # Actions completed on the parent. actions: [Action] @@ -197,7 +197,7 @@ type Comment { actions: [Action] # Action summaries against a comment. - action_summaries: [ActionSummary] + action_summaries: [ActionSummary]! # The asset that a comment was made on. asset: Asset @@ -440,7 +440,7 @@ type Asset { # Summary of all Actions against all entities associated with the Asset. # (likes, flags, etc.). Requires the `ADMIN` role. - action_summaries: [AssetActionSummary] + action_summaries: [AssetActionSummary!] # The date that the asset was created. created_at: Date diff --git a/plugins/coral-plugin-respect/client/components/RespectButton.js b/plugins/coral-plugin-respect/client/components/RespectButton.js index 9e851460e..417b978ad 100644 --- a/plugins/coral-plugin-respect/client/components/RespectButton.js +++ b/plugins/coral-plugin-respect/client/components/RespectButton.js @@ -5,6 +5,7 @@ import Icon from './Icon'; import {I18n} from 'coral-framework'; import cn from 'classnames'; import translations from '../translations.json'; +import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils'; const lang = new I18n(translations); @@ -14,8 +15,7 @@ class RespectButton extends Component { const {postRespect, showSignInDialog, deleteAction, commentId} = this.props; const {me, comment} = this.props.data; - const respect = comment.action_summaries[0]; - const respected = (respect && respect.current_user); + const myRespectActionSummary = getMyActionSummary('RespectActionSummary', comment); // If the current user does not exist, trigger sign in dialog. if (!me) { @@ -29,29 +29,33 @@ class RespectButton extends Component { return; } - if (!respected) { + if (myRespectActionSummary) { + deleteAction(myRespectActionSummary.current_user.id); + } else { postRespect({ item_id: commentId, item_type: 'COMMENTS' }); - } else { - deleteAction(respect.current_user.id); } } render() { const {comment} = this.props.data; - const respect = comment && comment.action_summaries && comment.action_summaries[0]; - const respected = respect && respect.current_user; - let count = respect ? respect.count : 0; + + if (!comment) { + return null; + } + + const myRespect = getMyActionSummary('RespectActionSummary', comment); + let count = getTotalActionCount('RespectActionSummary', comment); return (
    @@ -64,4 +68,3 @@ RespectButton.propTypes = { }; export default RespectButton; - diff --git a/plugins/coral-plugin-respect/client/containers/RespectButton.js b/plugins/coral-plugin-respect/client/containers/RespectButton.js index 047f7991f..bcf0e342d 100644 --- a/plugins/coral-plugin-respect/client/containers/RespectButton.js +++ b/plugins/coral-plugin-respect/client/containers/RespectButton.js @@ -10,6 +10,8 @@ import RespectButton from '../components/RespectButton'; // See https://dev-blog.apollodata.com/apollo-clients-new-imperative-store-api-6cb69318a1e3 // and https://github.com/apollographql/apollo-client/issues/1224 +const isRespectAction = (a) => a.__typename === 'RespectActionSummary'; + export const RESPECT_QUERY = gql` query RespectQuery($commentId: ID!) { comment(id: $commentId) { @@ -52,18 +54,21 @@ const withDeleteAction = graphql(gql` }, updateQueries: { RespectQuery: (prev) => { - if (get(prev, 'comment.action_summaries.0.current_user.id') !== id) { + const action_summaries = prev.comment.action_summaries; + const idx = action_summaries.findIndex(isRespectAction); + if (idx < 0 || get(action_summaries[idx], 'current_user.id') !== id) { return prev; } const next = { ...prev, comment: { ...prev.comment, - action_summaries: [{ - __typename: 'RespectActionSummary', - count: prev.comment.action_summaries[0].count - 1, - current_user: null, - }], + action_summaries: action_summaries.map( + (a, i) => i !== idx ? a : ({ + ...a, + count: a.count - 1, + current_user: null, + })), } }; return next; @@ -102,21 +107,40 @@ const withPostRespect = graphql(gql` }, updateQueries: { RespectQuery: (prev, {mutationResult, queryVariables}) => { - if (queryVariables.commentId !== respect.item_id || - get(prev, 'comment.action_summaries.0.current_user')) { + if (queryVariables.commentId !== respect.item_id) { return prev; } + + let action_summaries = prev.comment.action_summaries; + let idx = action_summaries.findIndex(isRespectAction); + + // Check whether we already respected this comment. + if (idx >= 0 && action_summaries[idx].current_user) { + return prev; + } + + if (idx < 0) { + + // Add initial action when it doesn't exist. + action_summaries = action_summaries.concat([{ + __typename: 'RespectActionSummary', + count: 0, + current_user: null, + }]); + idx = action_summaries.length - 1; + } + const respectAction = mutationResult.data.createRespect.respect; - const count = prev.comment.action_summaries[0] ? prev.comment.action_summaries[0].count : 0; const next = { ...prev, comment: { ...prev.comment, - action_summaries: [{ - __typename: 'RespectActionSummary', - count: count + 1, - current_user: respectAction, - }], + action_summaries: action_summaries.map( + (a, i) => i !== idx ? a : ({ + ...a, + count: a.count + 1, + current_user: respectAction, + })), } }; return next; @@ -138,4 +162,3 @@ const enhance = compose( ); export default enhance(RespectButton); - diff --git a/services/actions.js b/services/actions.js index 0bcb81af0..40bef65bb 100644 --- a/services/actions.js +++ b/services/actions.js @@ -48,10 +48,16 @@ module.exports = class ActionsService { * Finds actions in an array of ids. * @param {String} ids array of user identifiers (uuid) */ - static findByItemIdArray(item_ids) { - return ActionModel.find({ + static async findByItemIdArray(item_ids) { + let actions = await ActionModel.find({ 'item_id': {$in: item_ids} }); + + if (actions === null) { + return []; + } + + return actions; } /**