Merge branch 'master' into i18n-refactor

This commit is contained in:
gaba
2017-05-11 09:02:21 -07:00
43 changed files with 771 additions and 405 deletions
@@ -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;
@@ -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 (
@@ -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);
@@ -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 (
<div id="moderationList" className={`${styles.list} ${singleView ? styles.singleView : ''}`}>
<ul style={{paddingLeft: 0}}>
{
comments.length
? comments.map((comment, i) => {
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
return <Comment
key={i}
index={i}
comment={comment}
selected={i === selectedIndex}
suspectWords={props.suspectWords}
bannedWords={props.bannedWords}
actions={actionsMap[status]}
showBanUserDialog={props.showBanUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
currentAsset={props.currentAsset}
/>;
})
: <EmptyCard>{lang.t('modqueue.emptyqueue')}</EmptyCard>
}
</ul>
<LoadMore
comments={comments}
loadMore={loadMore}
sort={sort}
tab={activeTab}
showLoadMore={comments.length < commentCount}
assetId={props.assetId}
/>
</div>
);
};
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 (
<div id="moderationList" className={`${styles.list} ${singleView ? styles.singleView : ''}`}>
<ul style={{paddingLeft: 0}}>
{
comments.length
? comments.map((comment, i) => {
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
return <Comment
key={i}
index={i}
comment={comment}
selected={i === selectedIndex}
suspectWords={props.suspectWords}
bannedWords={props.bannedWords}
actions={actionsMap[status]}
showBanUserDialog={props.showBanUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
currentAsset={props.currentAsset}
/>;
})
: <EmptyCard>{lang.t('modqueue.emptyqueue')}</EmptyCard>
}
</ul>
<LoadMore
comments={comments}
loadMore={loadMore}
sort={sort}
tab={activeTab}
showLoadMore={comments.length < commentCount}
assetId={props.assetId}
/>
</div>
);
}
}
export default ModerationQueue;
@@ -66,15 +66,17 @@ const Comment = ({actions = [], comment, ...props}) => {
<div className={styles.sideActions}>
{links ? <span className={styles.hasLinks}><Icon name='error_outline'/> Contains Link</span> : null}
<div className={`actions ${styles.actions}`}>
{actions.map((action, i) =>
<ActionButton key={i}
type={action}
user={comment.user}
status={comment.status}
acceptComment={() => 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 <ActionButton key={i}
type={action}
user={comment.user}
status={comment.status}
active={active}
acceptComment={() => props.acceptComment({commentId: comment.id})}
rejectComment={() => props.rejectComment({commentId: comment.id})} />;
})}
</div>
</div>
</div>
@@ -1,9 +1,25 @@
import React, {PropTypes} from 'react';
import styles from './CommentCount.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
const CommentCount = props => (
<span className={styles.count}>{props.count}</span>
);
const CommentCount = ({count}) => {
let number = count;
// shorten large counts to abbreviations
if (number / 1e9 > 1) {
number = `${(number / 1e9).toFixed(1)}${lang.t('modqueue.billion')}`;
} else if (number / 1e6 > 1) {
number = `${(number / 1e6).toFixed(1)}${lang.t('modqueue.million')}`;
} else if (number / 1e3 > 1) {
number = `${(number / 1e3).toFixed(1)}${lang.t('modqueue.thousand')}`;
}
return (
<span className={styles.count}>{number}</span>
);
};
CommentCount.propTypes = {
count: PropTypes.number.isRequired
@@ -7,13 +7,16 @@ const LoadMore = ({comments, loadMore, sort, tab, assetId, showLoadMore}) =>
{
showLoadMore && <Button
className={styles.loadMore}
onClick={() =>
loadMore({
cursor: comments[comments.length - 1].created_at,
onClick={() => {
const lastComment = comments[comments.length - 1];
const cursor = lastComment ? lastComment.created_at : null;
return loadMore({
cursor,
sort,
tab,
asset_id: assetId
})}>
});
}}>
Load More
</Button>
}
@@ -6,7 +6,6 @@ fragment metrics on Asset {
created_at
commentCount
action_summaries {
type: __typename
actionCount
actionableItemCount
}
@@ -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,
@@ -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]
})
}
+26 -14
View File
@@ -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
}
};
}
});
@@ -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
@@ -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'}
]
}
],
+8 -2
View File
@@ -63,7 +63,10 @@
"impersonating": "Impersonating",
"offensive": "Offensive",
"spam/ads": "Spam/Ads",
"other": "Other"
"other": "Other",
"thousand": "k",
"million": "M",
"billion": "B"
},
"comment": {
"flagged": "flagged",
@@ -248,7 +251,10 @@
"impersonating": "Suplantación",
"offensive": "Ofensivo",
"spam/ads": "Spam/Propaganda",
"other": "Otros"
"other": "Otros",
"thousand": "m",
"million": "M",
"billion": "B"
},
"comment": {
"flagged": "marcado",
@@ -3,10 +3,8 @@
}
.apply {
position: absolute;
top: 38%;
transform: translateX(-50%);
right: 0;
float: right;
margin: 0 10px;
}
.wrapper ul {
@@ -10,8 +10,7 @@ export default ({handleChange, handleApply, changed, ...props}) => (
<form onSubmit={handleApply}>
<div className={styles.wrapper}>
<div className={styles.container}>
<h3>{lang.t('configure.title')}</h3>
<p>{lang.t('configure.description')}</p>
<h3>{lang.t('configureCommentStream.title')}</h3>
<Button
type="submit"
className={styles.apply}
@@ -19,6 +18,7 @@ export default ({handleChange, handleApply, changed, ...props}) => (
cStyle={changed ? 'green' : 'darkGrey'} >
{lang.t('configure.apply')}
</Button>
<p>{lang.t('configureCommentStream.description')}</p>
</div>
<ul>
<li>
@@ -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 <span className="comment__action-button comment__action-button--nowrap">{ children }</span>;
return (
<span className="comment__action-button comment__action-button--nowrap">
{children}
</span>
);
};
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 (
<div
className={commentClass}
id={`c_${comment.id}`}
style={{marginLeft: depth * 30}}>
style={{marginLeft: depth * 30}}
>
<hr aria-hidden={true} />
<div className={highlighted === comment.id ? 'highlighted-comment' : ''}>
<AuthorName
author={comment.user}/>
{ isStaff(comment.tags)
? <TagLabel>Staff</TagLabel>
: null }
<div
className={highlighted === comment.id ? 'highlighted-comment' : ''}
>
<AuthorName author={comment.user} />
{isStaff(comment.tags) ? <TagLabel>Staff</TagLabel> : null}
{ commentIsBest(comment)
{commentIsBest(comment)
? <TagLabel><BestIndicator /></TagLabel>
: null }
: null}
<PubDate created_at={comment.created_at} />
<Slot
fill="commentInfoBar"
@@ -186,47 +200,43 @@ class Comment extends React.Component {
commentId={comment.id}
inline
/>
{ (currentUser && (comment.user.id !== currentUser.id))
{currentUser && comment.user.id !== currentUser.id
? <span className={styles.topRightMenu}>
<TopRightMenu
comment={comment}
ignoreUser={ignoreUser}
addNotification={addNotification} />
addNotification={addNotification}
/>
</span>
: null
}
: null}
<Content body={comment.body} />
<Slot fill="commentContent" />
<div className="commentActionsLeft comment__action-container">
<Slot fill="commentReactions" inline />
<ActionButton>
{/* TODO implmement iPerformedThisAction for the like */}
<LikeButton
totalLikes={getTotalActionCount('LikeActionSummary', comment)}
like={likeSummary[0]}
id={comment.id}
postLike={postLike}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
currentUser={currentUser} />
</ActionButton>
{
!disableReply &&
<Slot
fill="commentReactions"
data={this.props.data}
root={this.props.root}
comment={comment}
commentId={comment.id}
inline
/>
{!disableReply &&
<ActionButton>
<ReplyButton
onClick={() => setActiveReplyBox(comment.id)}
parentCommentId={parentId || comment.id}
currentUserId={currentUser && currentUser.id}
banned={false} />
</ActionButton>
}
banned={false}
/>
</ActionButton>}
<ActionButton>
<IfUserCanModifyBest user={currentUser}>
<BestButton
isBest={commentIsBest(comment)}
addBest={addBestTag}
removeBest={removeBestTag} />
removeBest={removeBestTag}
/>
</IfUserCanModifyBest>
</ActionButton>
<Slot
@@ -252,12 +262,12 @@ class Comment extends React.Component {
postDontAgree={postDontAgree}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
currentUser={currentUser} />
currentUser={currentUser}
/>
</ActionButton>
</div>
</div>
{
activeReplyBox === comment.id
{activeReplyBox === comment.id
? <ReplyBox
commentPostedHandler={() => {
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)
? <IgnoredCommentTombstone key={reply.id} />
@@ -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 &&
<div className='coral-load-more-replies'>
comment={reply}
/>;
})}
{comment.replies &&
<div className="coral-load-more-replies">
<LoadMore
assetId={asset.id}
comments={comment.replies}
@@ -314,9 +321,9 @@ class Comment extends React.Component {
topLevel={false}
replyCount={comment.replyCount}
moreComments={comment.replyCount > comment.replies.length}
loadMore={loadMore}/>
</div>
}
loadMore={loadMore}
/>
</div>}
</div>
);
}
@@ -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}
@@ -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,
@@ -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) => {
@@ -1,10 +0,0 @@
mutation CreateLike ($like: CreateLikeInput!) {
createLike(like:$like) {
like {
id
}
errors {
translation_key
}
}
}
-71
View File
@@ -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 <div className={`${name}-container`}>
<button onClick={onLikeClick} className={`${name}-button ${liked ? 'likedButton' : ''}`}>
<span className={`${name}-button-text`}>{lang.t(liked ? 'liked' : 'like')}</span>
<i className={`${name}-icon material-icons`}
aria-hidden={true}>thumb_up</i>
<span className={`${name}-like-count`}>{count > 0 && count}</span>
</button>
</div>;
}
}
export default LikeButton;
const lang = new I18n(translations);
@@ -1,10 +0,0 @@
{
"en": {
"like": "Like",
"liked": "Liked"
},
"es": {
"like": "Me Gusta",
"liked": "Me Gustó"
}
}