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/services/fragmentMatcher.js b/client/coral-admin/src/services/fragmentMatcher.js index afb87d445..bf167cd1e 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'}, @@ -38,7 +37,6 @@ const fm = new IntrospectionFragmentMatcher({ name: 'Action', possibleTypes: [ {name: 'FlagAction'}, - {name: 'LikeAction'}, {name: 'DontAgreeAction'} ], }, @@ -47,7 +45,6 @@ const fm = new IntrospectionFragmentMatcher({ name: 'ActionSummary', possibleTypes: [ {name: 'FlagActionSummary'}, - {name: 'LikeActionSummary'}, {name: 'DontAgreeActionSummary'} ], }, @@ -57,7 +54,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 661cc9f96..5b60fbaa8 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 81105ff58..a4119b24a 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/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}) => ( +