diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 0c7591ddf..e4ca10743 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -17,7 +17,7 @@ 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} from 'coral-plugin-best/BestButton'; +import {BestButton, IfUserCanModifyBest, BEST_TAG, commentIsBest} from 'coral-plugin-best/BestButton'; import LoadMore from 'coral-embed-stream/src/LoadMore'; import styles from './Comment.css'; @@ -74,7 +74,13 @@ class Comment extends React.Component { id: PropTypes.string.isRequired, name: PropTypes.string.isRequired }).isRequired - }).isRequired + }).isRequired, + + // dispatch action to add a tag to a comment + addCommentTag: React.PropTypes.func, + + // dispatch action to remove a tag from a comment + removeCommentTag: React.PropTypes.func, } render () { @@ -93,13 +99,27 @@ class Comment extends React.Component { loadMore, setActiveReplyBox, activeReplyBox, - deleteAction + deleteAction, + addCommentTag, + removeCommentTag, } = this.props; const like = getActionSummary('LikeActionSummary', comment); const flag = getActionSummary('FlagActionSummary', comment); const dontagree = getActionSummary('DontAgreeActionSummary', comment); + // @TODO(bengo) Should I do this for addCommentTag and/or best button + + // @TODO(bengo) Would be best to only create these funcs on prop change + const addBestTag = () => addCommentTag({ + comment_id: comment.id, + tag: BEST_TAG, + }); + const removeBestTag = () => removeCommentTag({ + comment_id: comment.id, + tag: BEST_TAG, + }); + return (
{}} - removeBestTag={() => {}} /> + isBest={commentIsBest(comment)} + addBest={addBestTag} + removeBest={removeBestTag} />
diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index ac9c09eac..3290e2c8e 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -13,7 +13,7 @@ const {addNotification, clearNotification} = notificationActions; const {fetchAssetSuccess} = assetActions; import {queryStream} from 'coral-framework/graphql/queries'; -import {postComment, postFlag, postLike, postDontAgree, deleteAction} from 'coral-framework/graphql/mutations'; +import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag} from 'coral-framework/graphql/mutations'; import {editName} from 'coral-framework/actions/user'; import {updateCountCache} from 'coral-framework/actions/asset'; import {Notification, notificationActions, authActions, assetActions, pym} from 'coral-framework'; @@ -54,7 +54,13 @@ class Embed extends Component { data: React.PropTypes.shape({ loading: React.PropTypes.bool, error: React.PropTypes.object - }).isRequired + }).isRequired, + + // dispatch action to add a tag to a comment + addCommentTag: React.PropTypes.func, + + // dispatch action to remove a tag from a comment + removeCommentTag: React.PropTypes.func, } componentDidMount () { @@ -176,6 +182,8 @@ class Embed extends Component { postLike={this.props.postLike} postFlag={this.props.postFlag} postDontAgree={this.props.postDontAgree} + addCommentTag={this.props.addCommentTag} + removeCommentTag={this.props.removeCommentTag} getCounts={this.props.getCounts} updateCountCache={this.props.updateCountCache} loadMore={this.props.loadMore} @@ -250,6 +258,8 @@ export default compose( postFlag, postLike, postDontAgree, + addCommentTag, + removeCommentTag, deleteAction, queryStream )(Embed); diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js index a7f360d59..a1756c50e 100644 --- a/client/coral-embed-stream/src/Stream.js +++ b/client/coral-embed-stream/src/Stream.js @@ -13,7 +13,13 @@ class Stream extends React.Component { currentUser: PropTypes.shape({ username: PropTypes.string, id: PropTypes.string - }) + }), + + // dispatch action to add a tag to a comment + addCommentTag: React.PropTypes.func, + + // dispatch action to remove a tag from a comment + removeCommentTag: React.PropTypes.func, } constructor(props) { @@ -64,7 +70,9 @@ class Stream extends React.Component { loadMore, deleteAction, showSignInDialog, - refetch + refetch, + addCommentTag, + removeCommentTag, } = this.props; return ( @@ -83,6 +91,8 @@ class Stream extends React.Component { postLike={postLike} postFlag={postFlag} postDontAgree={postDontAgree} + addCommentTag={addCommentTag} + removeCommentTag={removeCommentTag} loadMore={loadMore} deleteAction={deleteAction} showSignInDialog={showSignInDialog} diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 46edc44e1..143b0f78c 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -217,6 +217,10 @@ hr { cursor: pointer; } +.comment__action-button[disabled] { + cursor: inherit; +} + .comment__action-button--nowrap { white-space: nowrap; } diff --git a/client/coral-framework/graphql/mutations/addCommentTag.graphql b/client/coral-framework/graphql/mutations/addCommentTag.graphql new file mode 100644 index 000000000..27d369775 --- /dev/null +++ b/client/coral-framework/graphql/mutations/addCommentTag.graphql @@ -0,0 +1,12 @@ +mutation AddCommentTag ($comment_id: ID!, $tag: String!) { + addCommentTag(comment_id:$comment_id, tag:$tag) { + comment { + tags { + name + } + } + errors { + translation_key + } + } +} diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index 67af2df4a..b65a6a339 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -4,6 +4,8 @@ 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'; +import REMOVE_COMMENT_TAG from './removeCommentTag.graphql'; import commentView from '../fragments/commentView.graphql'; @@ -122,3 +124,25 @@ export const deleteAction = graphql(DELETE_ACTION, { }); }}), }); + +export const addCommentTag = graphql(ADD_COMMENT_TAG, { + props: ({mutate}) => ({ + addCommentTag: (tag) => { + return mutate({ + variables: { + tag + } + }); + }}), +}); + +export const removeCommentTag = graphql(REMOVE_COMMENT_TAG, { + props: ({mutate}) => ({ + removeCommentTag: (tag) => { + return mutate({ + variables: { + tag + } + }); + }}), +}); diff --git a/client/coral-framework/graphql/mutations/removeCommentTag.graphql b/client/coral-framework/graphql/mutations/removeCommentTag.graphql new file mode 100644 index 000000000..2871a2fae --- /dev/null +++ b/client/coral-framework/graphql/mutations/removeCommentTag.graphql @@ -0,0 +1,12 @@ +mutation RemoveCommentTag ($comment_id: ID!, $tag: String!) { + removeCommentTag(comment_id:$comment_id, tag:$tag) { + comment { + tags { + name + } + } + errors { + translation_key + } + } +} diff --git a/client/coral-plugin-best/BestButton.js b/client/coral-plugin-best/BestButton.js index 3010db7c6..9af8b1638 100644 --- a/client/coral-plugin-best/BestButton.js +++ b/client/coral-plugin-best/BestButton.js @@ -3,6 +3,10 @@ import {I18n} from '../coral-framework'; import translations from './translations.json'; import classnames from 'classnames'; +// tag string for best comments +export const BEST_TAG = 'BEST'; +export const commentIsBest = ({tags} = {}) => Array.isArray(tags) && tags.some(t => t === BEST_TAG); + const name = 'coral-plugin-best'; const lang = new I18n(translations); @@ -21,17 +25,17 @@ export const IfUserCanModifyBest = ({user, children}) => { * Button that lets a moderator tag a comment as "Best". * Used to recognize really good comments. */ -class BestButton extends Component { +export class BestButton extends Component { static propTypes = { // whether the comment is already tagged as best isBest: PropTypes.bool.isRequired, // set that this comment is best - setBestTag: PropTypes.func.isRequired, + addBest: PropTypes.func.isRequired, // remove the best status - removeBestTag: PropTypes.func.isRequired, + removeBest: PropTypes.func.isRequired, } state = { @@ -40,18 +44,30 @@ class BestButton extends Component { constructor(props) { super(props); - this.onClickSetBest = this.onClickSetBest.bind(this); - this.onClickUnsetBest = this.onClickUnsetBest.bind(this); + this.onClickAddBest = this.onClickAddBest.bind(this); + this.onClickRemoveBest = this.onClickRemoveBest.bind(this); } - onClickSetBest(e) { + async onClickAddBest(e) { e.preventDefault(); - this.setState({isBest: true}); + const {addBest} = this.props; + if ( ! addBest) { + console.warn('BestButton#onClickAddBest called even though there is no addBest prop. doing nothing'); + return; + } + const addBestRet = await addBest(); + console.log('addBestRet', addBestRet); } - onClickUnsetBest(e) { + async onClickRemoveBest(e) { e.preventDefault(); - this.setState({isBest: false}); + const {removeBest} = this.props; + if ( ! removeBest) { + console.warn('BestButton#onClickAddBest called even though there is no removeBest prop. doing nothing'); + return; + } + const removeBestRet = await removeBest(); + console.log('removeBestRet', removeBestRet); } render() { @@ -59,10 +75,12 @@ class BestButton extends Component { // @TODO(bengo) Consider adding the comment__action classes to other buttons to add cursor:pointer and never wrap the icons // @TODO(bengo) Should I reuse another element like coral-ui button? Just doing what LikeButton does for now // Oh. I think that's styled for the admin. Don't use coral-ui button until the whole comment bottom bar does. - const {isBest} = this.state; + let {isBest} = this.state; + const {addBest, removeBest} = this.props; return
-
; } } - -export default BestButton;