diff --git a/.gitignore b/.gitignore index e8dfd14e7..a619f0988 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,6 @@ plugins/* !plugins/coral-plugin-facebook-auth !plugins/coral-plugin-respect !plugins/coral-plugin-offtopic +!plugins/coral-plugin-like **/node_modules/* diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index ae75b829e..3651ee939 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -3,9 +3,8 @@ import styles from './ModerationList.css'; import {Button} from 'coral-ui'; import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap'; -const ActionButton = ({type = '', status, ...props}) => { +const ActionButton = ({type = '', active, ...props}) => { const typeName = type.toLowerCase(); - const active = ((type === 'REJECT' && status === 'REJECTED') || (type === 'APPROVE' && status === 'ACCEPTED')); let text = menuActionsMap[type].text; if (text === 'Approve' && active) { @@ -25,7 +24,7 @@ const ActionButton = ({type = '', status, ...props}) => { }; ActionButton.propTypes = { - status: PropTypes.string + active: PropTypes.bool }; export default ActionButton; diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/containers/Dashboard/FlagWidget.js index 92de6c4d2..ea72cddc6 100644 --- a/client/coral-admin/src/containers/Dashboard/FlagWidget.js +++ b/client/coral-admin/src/containers/Dashboard/FlagWidget.js @@ -21,7 +21,7 @@ const FlagWidget = ({assets}) => { ? assets.map(asset => { let flagSummary = null; if (asset.action_summaries) { - flagSummary = asset.action_summaries.find(s => s.type === 'FlagAssetActionSummary'); + flagSummary = asset.action_summaries.find(s => s.__typename === 'FlagAssetActionSummary'); } return ( diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index eb59f369e..7a3aab909 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -5,7 +5,7 @@ import key from 'keymaster'; import isEqual from 'lodash/isEqual'; import styles from './components/styles.css'; -import {modQueueQuery} from '../../graphql/queries'; +import {modQueueQuery, getQueueCounts} from '../../graphql/queries'; import {banUser, setCommentStatus} from '../../graphql/mutations'; import {fetchSettings} from 'actions/settings'; @@ -220,6 +220,7 @@ const mapDispatchToProps = dispatch => ({ export default compose( connect(mapStateToProps, mapDispatchToProps), setCommentStatus, + getQueueCounts, modQueueQuery, banUser )(ModerationContainer); diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index f986b947b..c4bf0f85c 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -9,51 +9,67 @@ import translations from 'coral-admin/src/translations'; import LoadMore from './components/LoadMore'; const lang = new I18n(translations); -const ModerationQueue = ({comments, selectedIndex, commentCount, singleView, loadMore, activeTab, sort, ...props}) => { - return ( -
- - -
- ); -}; +class ModerationQueue extends React.Component { -ModerationQueue.propTypes = { - bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired, - suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired, - currentAsset: PropTypes.object, - showBanUserDialog: PropTypes.func.isRequired, - rejectComment: PropTypes.func.isRequired, - acceptComment: PropTypes.func.isRequired, - comments: PropTypes.array.isRequired -}; + static propTypes = { + bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired, + suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired, + currentAsset: PropTypes.object, + showBanUserDialog: PropTypes.func.isRequired, + rejectComment: PropTypes.func.isRequired, + acceptComment: PropTypes.func.isRequired, + comments: PropTypes.array.isRequired + } + + componentDidUpdate (prev) { + const {loadMore, comments, commentCount, sort, activeTab: tab, assetId: asset_id} = 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) { + loadMore({sort, tab, asset_id}); + } + } + + render () { + const {comments, selectedIndex, commentCount, singleView, loadMore, activeTab, sort, ...props} = this.props; + + return ( +
+ + +
+ ); + } +} export default ModerationQueue; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index 0fdf435bd..e5afd9726 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -66,15 +66,17 @@ const Comment = ({actions = [], comment, ...props}) => {
{links ? Contains Link : null}
- {actions.map((action, i) => - props.acceptComment({commentId: comment.id})} - rejectComment={() => props.rejectComment({commentId: comment.id})} - /> - )} + {actions.map((action, i) => { + const active = (action === 'REJECT' && comment.status === 'REJECTED') || + (action === 'APPROVE' && comment.status === 'ACCEPTED'); + return props.acceptComment({commentId: comment.id})} + rejectComment={() => props.rejectComment({commentId: comment.id})} />; + })}
diff --git a/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js b/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js index f16635486..c90d3cd14 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js @@ -7,13 +7,16 @@ const LoadMore = ({comments, loadMore, sort, tab, assetId, showLoadMore}) => { showLoadMore && } diff --git a/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql b/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql index c77fbc32b..726194143 100644 --- a/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql +++ b/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql @@ -6,7 +6,6 @@ fragment metrics on Asset { created_at commentCount action_summaries { - type: __typename actionCount actionableItemCount } diff --git a/client/coral-admin/src/graphql/mutations/index.js b/client/coral-admin/src/graphql/mutations/index.js index 4d62201d1..6b21c8421 100644 --- a/client/coral-admin/src/graphql/mutations/index.js +++ b/client/coral-admin/src/graphql/mutations/index.js @@ -44,6 +44,7 @@ export const suspendUser = graphql(SUSPEND_USER, { }) }); +const views = ['all', 'premod', 'flagged', 'accepted', 'rejected']; export const setCommentStatus = graphql(SET_COMMENT_STATUS, { props: ({mutate}) => ({ acceptComment: ({commentId}) => { @@ -54,7 +55,9 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, { }, updateQueries: { ModQueue: (oldData) => { - const comment = oldData.all.find(c => c.id === commentId); + const comment = views.reduce((comment, view) => { + return comment ? comment : oldData[view].find(c => c.id === commentId); + }, null); let accepted; let acceptedCount = oldData.acceptedCount; @@ -76,10 +79,10 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, { return { ...oldData, - premodCount, - flaggedCount, - acceptedCount, - rejectedCount, + premodCount: Math.max(0, premodCount), + flaggedCount: Math.max(0, flaggedCount), + acceptedCount: Math.max(0, acceptedCount), + rejectedCount: Math.max(0, rejectedCount), premod, flagged, accepted, @@ -97,7 +100,9 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, { }, updateQueries: { ModQueue: (oldData) => { - const comment = oldData.all.find(c => c.id === commentId); + const comment = views.reduce((comment, view) => { + return comment ? comment : oldData[view].find(c => c.id === commentId); + }, null); let rejected; let rejectedCount = oldData.rejectedCount; @@ -119,10 +124,10 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, { return { ...oldData, - premodCount, - flaggedCount, - acceptedCount, - rejectedCount, + premodCount: Math.max(0, premodCount), + flaggedCount: Math.max(0, flaggedCount), + acceptedCount: Math.max(0, acceptedCount), + rejectedCount: Math.max(0, rejectedCount), premod, flagged, accepted, diff --git a/client/coral-admin/src/graphql/queries/getQueueCounts.graphql b/client/coral-admin/src/graphql/queries/getQueueCounts.graphql new file mode 100644 index 000000000..4a7c00d87 --- /dev/null +++ b/client/coral-admin/src/graphql/queries/getQueueCounts.graphql @@ -0,0 +1,22 @@ +query Counts ($asset_id: ID) { + allCount: commentCount(query: { + asset_id: $asset_id + }) + acceptedCount: commentCount(query: { + statuses: [ACCEPTED], + asset_id: $asset_id + }) + premodCount: commentCount(query: { + statuses: [PREMOD], + asset_id: $asset_id + }) + rejectedCount: commentCount(query: { + statuses: [REJECTED], + asset_id: $asset_id + }) + flaggedCount: commentCount(query: { + action_type: FLAG, + asset_id: $asset_id, + statuses: [NONE, PREMOD] + }) +} diff --git a/client/coral-admin/src/graphql/queries/index.js b/client/coral-admin/src/graphql/queries/index.js index 9d2357ce7..f26c8464e 100644 --- a/client/coral-admin/src/graphql/queries/index.js +++ b/client/coral-admin/src/graphql/queries/index.js @@ -4,6 +4,7 @@ import MOD_QUEUE_QUERY from './modQueueQuery.graphql'; import MOD_QUEUE_LOAD_MORE from './loadMore.graphql'; import MOD_USER_FLAGGED_QUERY from './modUserFlaggedQuery.graphql'; import METRICS from './metricsQuery.graphql'; +import GET_QUEUE_COUNTS from './getQueueCounts.graphql'; export const modQueueQuery = graphql(MOD_QUEUE_QUERY, { options: ({params: {id = null}}) => { @@ -33,34 +34,34 @@ export const getMetrics = graphql(METRICS, { } }); -export const loadMore = (fetchMore) => ({limit, cursor, sort, tab, asset_id}) => { - let statuses; +export const loadMore = (fetchMore) => ({limit = 10, cursor, sort, tab, asset_id}) => { + let variables = { + limit, + cursor, + sort, + asset_id + }; switch(tab) { case 'all': - statuses = null; + variables.statuses = null; break; case 'accepted': - statuses = ['ACCEPTED']; + variables.statuses = ['ACCEPTED']; break; case 'premod': - statuses = ['PREMOD']; + variables.statuses = ['PREMOD']; break; case 'flagged': - statuses = ['NONE', 'PREMOD']; + variables.statuses = ['NONE', 'PREMOD']; + variables.action_type = 'FLAG'; break; case 'rejected': - statuses = ['REJECTED']; + variables.statuses = ['REJECTED']; break; } return fetchMore({ query: MOD_QUEUE_LOAD_MORE, - variables: { - limit, - cursor, - sort, - statuses, - asset_id - }, + variables, updateQuery: (oldData, {fetchMoreResult:{comments}}) => { return { ...oldData, @@ -93,3 +94,14 @@ export const modQueueResort = (id, fetchMore) => (sort) => { updateQuery: (oldData, {fetchMoreResult:{data}}) => data }); }; + +export const getQueueCounts = graphql(GET_QUEUE_COUNTS, { + options: ({params: {id = null}}) => { + return { + pollInterval: 5000, + variables: { + asset_id: id + } + }; + } +}); diff --git a/client/coral-admin/src/graphql/queries/loadMore.graphql b/client/coral-admin/src/graphql/queries/loadMore.graphql index 56966a804..a42c7b2bf 100644 --- a/client/coral-admin/src/graphql/queries/loadMore.graphql +++ b/client/coral-admin/src/graphql/queries/loadMore.graphql @@ -1,7 +1,7 @@ #import "../fragments/commentView.graphql" -query LoadMoreModQueue($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!]) { - comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort}) { +query LoadMoreModQueue($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) { + comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort, action_type: $action_type}) { ...commentView action_summaries { count diff --git a/client/coral-admin/src/services/fragmentMatcher.js b/client/coral-admin/src/services/fragmentMatcher.js index afb87d445..531708f31 100644 --- a/client/coral-admin/src/services/fragmentMatcher.js +++ b/client/coral-admin/src/services/fragmentMatcher.js @@ -20,7 +20,6 @@ const fm = new IntrospectionFragmentMatcher({ name: 'Response', possibleTypes: [ {name: 'CreateCommentResponse'}, - {name: 'CreateLikeResponse'}, {name: 'CreateFlagResponse'}, {name: 'CreateDontAgreeResponse'}, {name: 'DeleteActionResponse'}, @@ -37,8 +36,8 @@ const fm = new IntrospectionFragmentMatcher({ kind: 'INTERFACE', name: 'Action', possibleTypes: [ + {name: 'DefaultAction'}, {name: 'FlagAction'}, - {name: 'LikeAction'}, {name: 'DontAgreeAction'} ], }, @@ -46,8 +45,8 @@ const fm = new IntrospectionFragmentMatcher({ kind: 'INTERFACE', name: 'ActionSummary', possibleTypes: [ + {name: 'DefaultActionSummary'}, {name: 'FlagActionSummary'}, - {name: 'LikeActionSummary'}, {name: 'DontAgreeActionSummary'} ], }, @@ -57,7 +56,6 @@ const fm = new IntrospectionFragmentMatcher({ possibleTypes: [ {name: 'DefaultAssetActionSummary'}, {name: 'FlagAssetActionSummary'}, - {name: 'LikeAssetActionSummary'} ] } ], diff --git a/client/coral-embed-stream/src/components/Comment.js b/client/coral-embed-stream/src/components/Comment.js index 7ce143c55..4349fe38d 100644 --- a/client/coral-embed-stream/src/components/Comment.js +++ b/client/coral-embed-stream/src/components/Comment.js @@ -1,11 +1,3 @@ -// this component will -// render its children -// render a like button -// render a permalink button -// render a reply button -// render a flag button -// translate things? - import React, {PropTypes} from 'react'; import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton'; @@ -16,25 +8,33 @@ import Content from 'coral-plugin-commentcontent/CommentContent'; import PubDate from 'coral-plugin-pubdate/PubDate'; import {ReplyBox, ReplyButton} from 'coral-plugin-replies'; import FlagComment from 'coral-plugin-flags/FlagComment'; -import LikeButton from 'coral-plugin-likes/LikeButton'; -import {BestButton, IfUserCanModifyBest, BEST_TAG, commentIsBest, BestIndicator} from 'coral-plugin-best/BestButton'; +import { + BestButton, + IfUserCanModifyBest, + BEST_TAG, + commentIsBest, + BestIndicator +} from 'coral-plugin-best/BestButton'; import Slot from 'coral-framework/components/Slot'; import LoadMore from './LoadMore'; import IgnoredCommentTombstone from './IgnoredCommentTombstone'; import {TopRightMenu} from './TopRightMenu'; -import {getActionSummary, getTotalActionCount, iPerformedThisAction} from 'coral-framework/utils'; +import {getActionSummary, iPerformedThisAction} from 'coral-framework/utils'; import styles from './Comment.css'; -const isStaff = (tags) => !tags.every((t) => t.name !== 'STAFF') ; +const isStaff = tags => !tags.every(t => t.name !== 'STAFF'); -// hold actions links (e.g. Like, Reply) along the comment footer +// hold actions links (e.g. Reply) along the comment footer const ActionButton = ({children}) => { - return { children }; + return ( + + {children} + + ); }; class Comment extends React.Component { - constructor(props) { super(props); this.state = {replyBoxVisible: false}; @@ -49,7 +49,6 @@ class Comment extends React.Component { setActiveReplyBox: PropTypes.func.isRequired, showSignInDialog: PropTypes.func.isRequired, postFlag: PropTypes.func.isRequired, - postLike: PropTypes.func.isRequired, deleteAction: PropTypes.func.isRequired, parentId: PropTypes.string, highlighted: PropTypes.string, @@ -80,7 +79,8 @@ class Comment extends React.Component { PropTypes.shape({ body: PropTypes.string.isRequired, id: PropTypes.string.isRequired - })), + }) + ), user: PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string.isRequired @@ -97,10 +97,10 @@ class Comment extends React.Component { removeCommentTag: React.PropTypes.func, // dispatch action to ignore another user - ignoreUser: React.PropTypes.func, - } + ignoreUser: React.PropTypes.func + }; - render () { + render() { const { comment, parentId, @@ -110,7 +110,6 @@ class Comment extends React.Component { postItem, addNotification, showSignInDialog, - postLike, highlighted, postFlag, postDontAgree, @@ -124,12 +123,14 @@ class Comment extends React.Component { disableReply, commentIsIgnored, maxCharCount, - charCountEnable, + charCountEnable } = this.props; - const likeSummary = getActionSummary('LikeActionSummary', comment); const flagSummary = getActionSummary('FlagActionSummary', comment); - const dontAgreeSummary = getActionSummary('DontAgreeActionSummary', comment); + const dontAgreeSummary = getActionSummary( + 'DontAgreeActionSummary', + comment + ); let myFlag = null; if (iPerformedThisAction('FlagActionSummary', comment)) { myFlag = flagSummary.find(s => s.current_user); @@ -137,46 +138,59 @@ class Comment extends React.Component { myFlag = dontAgreeSummary.find(s => s.current_user); } - let commentClass = parentId ? `reply ${styles.Reply}` : `comment ${styles.Comment}`; + let commentClass = parentId + ? `reply ${styles.Reply}` + : `comment ${styles.Comment}`; commentClass += comment.id === 'pending' ? ` ${styles.pendingComment}` : ''; // call a function, and if it errors, call addNotification('error', ...) (e.g. to show user a snackbar) - const notifyOnError = (fn, errorToMessage) => async function (...args) { - if (typeof errorToMessage !== 'function') {errorToMessage = (error) => error.message;} - try { - return await fn(...args); - } catch (error) { - addNotification('error', errorToMessage(error)); - throw error; - } - }; + const notifyOnError = (fn, errorToMessage) => + async function(...args) { + if (typeof errorToMessage !== 'function') { + errorToMessage = error => error.message; + } + try { + return await fn(...args); + } catch (error) { + addNotification('error', errorToMessage(error)); + throw error; + } + }; - const addBestTag = notifyOnError(() => addCommentTag({ - id: comment.id, - tag: BEST_TAG, - }), () => 'Failed to tag comment as best'); + const addBestTag = notifyOnError( + () => + addCommentTag({ + id: comment.id, + tag: BEST_TAG + }), + () => 'Failed to tag comment as best' + ); - const removeBestTag = notifyOnError(() => removeCommentTag({ - id: comment.id, - tag: BEST_TAG, - }), () => 'Failed to remove best comment tag'); + const removeBestTag = notifyOnError( + () => + removeCommentTag({ + id: comment.id, + tag: BEST_TAG + }), + () => 'Failed to remove best comment tag' + ); return (
+ style={{marginLeft: depth * 30}} + >
-
- - { isStaff(comment.tags) - ? Staff - : null } +
+ + {isStaff(comment.tags) ? Staff : null} - { commentIsBest(comment) + {commentIsBest(comment) ? - : null } + : null} - { (currentUser && (comment.user.id !== currentUser.id)) + {currentUser && comment.user.id !== currentUser.id ? + addNotification={addNotification} + /> - : null - } + : null}
- - - {/* TODO implmement iPerformedThisAction for the like */} - - - { - !disableReply && + + {!disableReply && setActiveReplyBox(comment.id)} parentCommentId={parentId || comment.id} currentUserId={currentUser && currentUser.id} - banned={false} /> - - } + banned={false} + /> + } + removeBest={removeBestTag} + /> + currentUser={currentUser} + />
- { - activeReplyBox === comment.id + {activeReplyBox === comment.id ? { setActiveReplyBox(''); @@ -269,11 +279,10 @@ class Comment extends React.Component { addNotification={addNotification} authorId={currentUser.id} postItem={postItem} - assetId={asset.id} /> - : null - } - { - comment.replies && + assetId={asset.id} + /> + : null} + {comment.replies && comment.replies.map(reply => { return commentIsIgnored(reply) ? @@ -290,7 +299,6 @@ class Comment extends React.Component { asset={asset} highlighted={highlighted} currentUser={currentUser} - postLike={postLike} postFlag={postFlag} deleteAction={deleteAction} addCommentTag={addCommentTag} @@ -301,12 +309,11 @@ class Comment extends React.Component { showSignInDialog={showSignInDialog} reactKey={reply.id} key={reply.id} - comment={reply} />; - }) - } - { - comment.replies && -
+ comment={reply} + />; + })} + {comment.replies && +
comment.replies.length} - loadMore={loadMore}/> -
- } + loadMore={loadMore} + /> +
}
); } diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index eb30dc16c..46ac75dca 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -29,7 +29,6 @@ class Stream extends React.Component { postItem, addNotification, postFlag, - postLike, postDontAgree, loadMore, deleteAction, @@ -127,7 +126,6 @@ class Stream extends React.Component { asset={asset} currentUser={user} highlighted={comment.id} - postLike={this.props.postLike} postFlag={this.props.postFlag} postDontAgree={this.props.postDontAgree} loadMore={this.props.loadMore} @@ -165,7 +163,6 @@ class Stream extends React.Component { postItem={postItem} asset={asset} currentUser={user} - postLike={postLike} postFlag={postFlag} postDontAgree={postDontAgree} addCommentTag={addCommentTag} diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js index 49f234dab..16714df06 100644 --- a/client/coral-embed-stream/src/containers/Stream.js +++ b/client/coral-embed-stream/src/containers/Stream.js @@ -6,7 +6,7 @@ import uniqBy from 'lodash/uniqBy'; import sortBy from 'lodash/sortBy'; import isNil from 'lodash/isNil'; import {NEW_COMMENT_COUNT_POLL_INTERVAL} from '../constants/stream'; -import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag, ignoreUser} from 'coral-framework/graphql/mutations'; +import {postComment, postFlag, postDontAgree, deleteAction, addCommentTag, removeCommentTag, ignoreUser} from 'coral-framework/graphql/mutations'; import {notificationActions, authActions} from 'coral-framework'; import {editName} from 'coral-framework/actions/user'; import {setCommentCountCache, setActiveReplyBox} from '../actions/stream'; @@ -217,7 +217,6 @@ const mapStateToProps = state => ({ auth: state.auth.toJS(), commentCountCache: state.stream.commentCountCache, activeReplyBox: state.stream.activeReplyBox, - commentId: state.stream.commentId, assetId: state.stream.assetId, assetUrl: state.stream.assetUrl, @@ -239,7 +238,6 @@ export default compose( connect(mapStateToProps, mapDispatchToProps), postComment, postFlag, - postLike, postDontAgree, addCommentTag, removeCommentTag, diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index b1ea57ae1..bdd7fd1a1 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -1,7 +1,6 @@ import {graphql} from 'react-apollo'; import POST_COMMENT from './postComment.graphql'; import POST_FLAG from './postFlag.graphql'; -import POST_LIKE from './postLike.graphql'; import POST_DONT_AGREE from './postDontAgree.graphql'; import DELETE_ACTION from './deleteAction.graphql'; import ADD_COMMENT_TAG from './addCommentTag.graphql'; @@ -83,17 +82,6 @@ export const postComment = graphql(POST_COMMENT, { }), }); -export const postLike = graphql(POST_LIKE, { - props: ({mutate}) => ({ - postLike: (like) => { - return mutate({ - variables: { - like - } - }); - }}), -}); - export const postFlag = graphql(POST_FLAG, { props: ({mutate}) => ({ postFlag: (flag) => { diff --git a/client/coral-framework/graphql/mutations/postLike.graphql b/client/coral-framework/graphql/mutations/postLike.graphql deleted file mode 100644 index 350904352..000000000 --- a/client/coral-framework/graphql/mutations/postLike.graphql +++ /dev/null @@ -1,10 +0,0 @@ -mutation CreateLike ($like: CreateLikeInput!) { - createLike(like:$like) { - like { - id - } - errors { - translation_key - } - } -} diff --git a/client/coral-plugin-likes/LikeButton.js b/client/coral-plugin-likes/LikeButton.js deleted file mode 100644 index 28382dbc2..000000000 --- a/client/coral-plugin-likes/LikeButton.js +++ /dev/null @@ -1,71 +0,0 @@ -import React, {Component, PropTypes} from 'react'; -import {I18n} from '../coral-framework'; -import translations from './translations.json'; - -const name = 'coral-plugin-likes'; - -class LikeButton extends Component { - - static propTypes = { - like: PropTypes.shape({ - current: PropTypes.object, - count: PropTypes.number - }), - id: PropTypes.string, - postLike: PropTypes.func.isRequired, - deleteAction: PropTypes.func.isRequired, - showSignInDialog: PropTypes.func.isRequired, - currentUser: PropTypes.shape({ - banned: PropTypes.boolean - }), - } - - state = { - localPost: null, // Set to the ID of an action if one is posted - localDelete: false // Set to true is the user deletes an action, unless localPost is already set. - } - - 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; - if (localPost) {count += 1;} - if (localDelete) {count -= 1;} - - const onLikeClick = () => { - if (!currentUser) { - showSignInDialog(); - return; - } - if (currentUser.banned) { - return; - } - if (!liked) { // this comment has not yet been liked by this user. - this.setState({localPost: 'temp'}); - postLike({ - item_id: id, - item_type: 'COMMENTS' - }).then(({data}) => { - this.setState({localPost: data.createLike.like.id}); - }); - } else { - this.setState((prev) => prev.localPost ? {...prev, localPost: null} : {...prev, localDelete: true}); - deleteAction(localPost || like.current_user.id); - } - }; - - return
- -
; - } -} - -export default LikeButton; - -const lang = new I18n(translations); diff --git a/graph/resolvers/action.js b/graph/resolvers/action.js index 1bb8d077e..393024fe1 100644 --- a/graph/resolvers/action.js +++ b/graph/resolvers/action.js @@ -5,8 +5,6 @@ const Action = { return 'DontAgreeAction'; case 'FLAG': return 'FlagAction'; - case 'LIKE': - return 'LikeAction'; } }, diff --git a/graph/resolvers/action_summary.js b/graph/resolvers/action_summary.js index ac2154de8..f9c2ad324 100644 --- a/graph/resolvers/action_summary.js +++ b/graph/resolvers/action_summary.js @@ -3,8 +3,6 @@ const ActionSummary = { switch (action_type) { case 'FLAG': return 'FlagActionSummary'; - case 'LIKE': - return 'LikeActionSummary'; case 'DONTAGREE': return 'DontAgreeActionSummary'; } diff --git a/graph/resolvers/index.js b/graph/resolvers/index.js index 39392b223..cbeb6dbf9 100644 --- a/graph/resolvers/index.js +++ b/graph/resolvers/index.js @@ -12,7 +12,6 @@ const FlagAction = require('./flag_action'); const DontAgreeAction = require('./dont_agree_action'); const DontAgreeActionSummary = require('./dont_agree_action_summary'); const GenericUserError = require('./generic_user_error'); -const LikeAction = require('./like_action'); const RootMutation = require('./root_mutation'); const RootQuery = require('./root_query'); const Settings = require('./settings'); @@ -36,7 +35,6 @@ let resolvers = { DontAgreeAction, DontAgreeActionSummary, GenericUserError, - LikeAction, RootMutation, RootQuery, Settings, diff --git a/graph/resolvers/like_action.js b/graph/resolvers/like_action.js deleted file mode 100644 index 12d10f81e..000000000 --- a/graph/resolvers/like_action.js +++ /dev/null @@ -1,5 +0,0 @@ -const LikeAction = { - -}; - -module.exports = LikeAction; diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index d669869cd..e3e3ae1d7 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -5,9 +5,6 @@ const RootMutation = { createComment(_, {comment}, {mutators: {Comment}}) { return wrapResponse('comment')(Comment.create(comment)); }, - createLike(_, {like: {item_id, item_type}}, {mutators: {Action}}) { - return wrapResponse('like')(Action.create({item_id, item_type, action_type: 'LIKE'})); - }, createFlag(_, {flag: {item_id, item_type, reason, message}}, {mutators: {Action}}) { return wrapResponse('flag')(Action.create({item_id, item_type, action_type: 'FLAG', group_id: reason, metadata: {message}})); }, diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index e7f29ebfd..a4ebe98b3 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -103,9 +103,6 @@ enum COMMENT_STATUS { # The types of action there are as enum's. enum ACTION_TYPE { - # Represents a LikeAction. - LIKE - # Represents a FlagAction. FLAG @@ -295,41 +292,6 @@ type FlagAssetActionSummary implements AssetActionSummary { actionableItemCount: Int } -# A summary of counts related to all the Likes on an Asset. -type LikeAssetActionSummary implements AssetActionSummary { - - # Number of likes associated with actionable types on this this Asset. - actionCount: Int - - # Number of unique actionable types that are referenced by the likes. - actionableItemCount: Int -} - -# LikeAction is used by users who "like" a specific entity. -type LikeAction implements Action { - - # The ID of the action. - id: ID! - - # The author of the action. - user: User - - # The time when the Action was updated. - updated_at: Date - - # The time when the Action was created. - created_at: Date -} - -# LikeActionSummary is counts the amount of "likes" that a specific entity has. -type LikeActionSummary implements ActionSummary { - - # The count of likes against the parent entity. - count: Int! - - current_user: LikeAction -} - # A FLAG action that contains flag metadata. type FlagAction implements Action { @@ -541,9 +503,6 @@ enum USER_STATUS { # Metrics for the assets. enum ASSET_METRICS_SORT { - # Represents a LikeAction. - LIKE - # Represents a FlagAction. FLAG @@ -629,15 +588,6 @@ enum ACTION_ITEM_TYPE { USERS } -input CreateLikeInput { - - # The item's id for which we are to create a like. - item_id: ID! - - # The type of the item for which we are to create the like. - item_type: ACTION_ITEM_TYPE! -} - enum TAG_TYPE { STAFF } @@ -658,15 +608,6 @@ input CreateCommentInput { } -type CreateLikeResponse implements Response { - - # The like that was created. - like: LikeAction - - # An array of errors relating to the mutation that occurred. - errors: [UserError] -} - input CreateFlagInput { # The item's id for which we are to create a flag. @@ -786,9 +727,6 @@ type RootMutation { # Creates a comment on the asset. createComment(comment: CreateCommentInput!): CreateCommentResponse - # Creates a like on an entity. - createLike(like: CreateLikeInput!): CreateLikeResponse - # Creates a flag on an entity. createFlag(flag: CreateFlagInput!): CreateFlagResponse diff --git a/package.json b/package.json index 250c0549f..bfca21643 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "talk", - "version": "1.5.0", + "version": "1.6.0", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", "scripts": { diff --git a/plugins/coral-plugin-like/client/.babelrc b/plugins/coral-plugin-like/client/.babelrc new file mode 100644 index 000000000..60be246eb --- /dev/null +++ b/plugins/coral-plugin-like/client/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "add-module-exports", + "transform-class-properties", + "transform-decorators-legacy", + "transform-object-assign", + "transform-object-rest-spread", + "transform-async-to-generator", + "transform-react-jsx" + ] +} \ No newline at end of file diff --git a/plugins/coral-plugin-like/client/.eslintrc.json b/plugins/coral-plugin-like/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/plugins/coral-plugin-like/client/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} diff --git a/plugins/coral-plugin-like/client/components/Icon.js b/plugins/coral-plugin-like/client/components/Icon.js new file mode 100644 index 000000000..c24841e97 --- /dev/null +++ b/plugins/coral-plugin-like/client/components/Icon.js @@ -0,0 +1,6 @@ +import React from 'react'; +import cn from 'classnames'; + +export default ({className}) => ( +