First implementation of ignore user plugin

This commit is contained in:
Chi Vinh Le
2017-09-04 15:40:39 +07:00
parent c07eaf0edb
commit ffef2f640d
25 changed files with 217 additions and 117 deletions
+1
View File
@@ -21,5 +21,6 @@ plugins/*
!plugins/talk-plugin-sort-most-respected
!plugins/talk-plugin-author-menu
!plugins/talk-plugin-member-since
!plugins/talk-plugin-ignore-user
node_modules
+1
View File
@@ -37,5 +37,6 @@ plugins/*
!plugins/talk-plugin-sort-most-respected
!plugins/talk-plugin-author-menu
!plugins/talk-plugin-member-since
!plugins/talk-plugin-ignore-user
**/node_modules/*
@@ -126,7 +126,6 @@ class AllCommentsPane extends React.Component {
root,
comments,
commentClassNames,
ignoreUser,
setActiveReplyBox,
activeReplyBox,
notify,
@@ -173,7 +172,6 @@ class AllCommentsPane extends React.Component {
currentUser={currentUser}
postFlag={postFlag}
postDontAgree={postDontAgree}
ignoreUser={ignoreUser}
commentIsIgnored={commentIsIgnored}
loadMore={loadNewReplies}
deleteAction={deleteAction}
@@ -66,17 +66,6 @@
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
}
.topRight .popover {
margin-top: 1em;
right: 0px;
}
.topRight .link.active,
.topRight .active .link {
padding-bottom: 0.125em;
border-bottom: 2px solid currentColor;
}
.editCommentForm {
margin-bottom: 10px;
}
@@ -175,4 +164,4 @@
.username {
margin-right: 5px;
}
}
@@ -16,7 +16,6 @@ import mapValues from 'lodash/mapValues';
import LoadMore from './LoadMore';
import {getEditableUntilDate} from './util';
import {findCommentWithId} from '../graphql/utils';
import {TopRightMenu} from './TopRightMenu';
import CommentContent from './CommentContent';
import Slot from 'coral-framework/components/Slot';
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
@@ -187,9 +186,6 @@ export default class Comment extends React.Component {
// given a comment, return whether it should be rendered as ignored
commentIsIgnored: PropTypes.func,
// dispatch action to ignore another user
ignoreUser: PropTypes.func,
// edit a comment, passed (id, asset_id, { body })
editComment: PropTypes.func,
@@ -317,7 +313,6 @@ export default class Comment extends React.Component {
comment,
postFlag,
parentId,
ignoreUser,
highlighted,
postComment,
currentUser,
@@ -480,16 +475,6 @@ export default class Comment extends React.Component {
}
</span>
}
{ isActive && (currentUser && (comment.user.id !== currentUser.id)) &&
/* TopRightMenu allows currentUser to ignore other users' comments */
<span className={cn(styles.topRight, styles.topRightMenu)}>
<TopRightMenu
comment={comment}
ignoreUser={ignoreUser}
notify={notify} />
</span>
}
{ !isActive &&
<InactiveCommentLabel status={comment.status}/>
}
@@ -600,7 +585,6 @@ export default class Comment extends React.Component {
postFlag={postFlag}
deleteAction={deleteAction}
loadMore={loadMore}
ignoreUser={ignoreUser}
charCountEnable={charCountEnable}
maxCharCount={maxCharCount}
showSignInDialog={showSignInDialog}
@@ -64,7 +64,6 @@ class Stream extends React.Component {
postDontAgree,
deleteAction,
showSignInDialog,
ignoreUser,
loadNewReplies,
auth: {user},
emit,
@@ -90,7 +89,6 @@ class Stream extends React.Component {
data={data}
root={root}
commentClassNames={commentClassNames}
ignoreUser={ignoreUser}
setActiveReplyBox={setActiveReplyBox}
activeReplyBox={activeReplyBox}
notify={notify}
@@ -133,7 +131,6 @@ class Stream extends React.Component {
postDontAgree,
deleteAction,
showSignInDialog,
ignoreUser,
activeStreamTab,
setActiveStreamTab,
loadNewReplies,
@@ -183,7 +180,6 @@ class Stream extends React.Component {
root={root}
comments={comments}
commentClassNames={commentClassNames}
ignoreUser={ignoreUser}
setActiveReplyBox={setActiveReplyBox}
activeReplyBox={activeReplyBox}
notify={notify}
@@ -322,9 +318,6 @@ Stream.propTypes = {
notify: PropTypes.func.isRequired,
postComment: PropTypes.func.isRequired,
// dispatch action to ignore another user
ignoreUser: React.PropTypes.func,
// edit a comment, passed (id, asset_id, { body })
editComment: React.PropTypes.func
};
@@ -1,60 +0,0 @@
import React, {PropTypes} from 'react';
import {IgnoreUserWizard} from './IgnoreUserWizard';
import Toggleable from './Toggleable';
// TopRightMenu appears as a dropdown in the top right of the comment.
// when you click the down cehvron, it expands and shows IgnoreUserWizard
// when you click 'cancel' in the wizard, it closes the menu
export class TopRightMenu extends React.Component {
static propTypes = {
// comment on which this menu appears
comment: PropTypes.shape({
user: PropTypes.shape({
id: PropTypes.string.isRequired,
username: PropTypes.string.isRequired
}).isRequired
}).isRequired,
ignoreUser: PropTypes.func,
// show notification to the user (e.g. for errors)
notify: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
this.state = {
timesReset: 0
};
}
render() {
const {comment, ignoreUser, notify} = this.props;
// timesReset is used as Toggleable key so it re-renders on reset (closing the toggleable)
const reset = () => this.setState({timesReset: this.state.timesReset + 1});
const ignoreUserAndCloseMenuAndNotifyOnError = async ({id}) => {
// close menu
reset();
// ignore user
try {
await ignoreUser({id});
} catch (error) {
notify('error', 'Failed to ignore user');
throw error;
}
};
return (
<Toggleable key={this.state.timesReset} className="talk-stream-comment-chevron">
<div style={{position: 'absolute', right: 0, zIndex: 1}}>
<IgnoreUserWizard
user={comment.user}
cancel={reset}
ignoreUser={ignoreUserAndCloseMenuAndNotifyOnError}
/>
</div>
</Toggleable>
);
}
}
@@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux';
import {ADDTL_COMMENTS_ON_LOAD_MORE, THREADING_LEVEL} from '../constants/stream';
import {
withPostComment, withPostFlag, withPostDontAgree,
withDeleteAction, withIgnoreUser, withEditComment
withDeleteAction, withEditComment
} from 'coral-framework/graphql/mutations';
import * as authActions from 'coral-embed-stream/src/actions/auth';
@@ -374,7 +374,6 @@ export default compose(
withPostComment,
withPostFlag,
withPostDontAgree,
withIgnoreUser,
withDeleteAction,
withEditComment,
)(StreamContainer);
@@ -108,18 +108,6 @@ export default {
`,
},
mutations: {
IgnoreUser: ({variables}) => ({
updateQueries: {
CoralEmbedStream_Embed: (previousData) => {
const ignoredUserId = variables.id;
const updated = update(previousData, {me: {ignoredUsers: {$push: [{
id: ignoredUserId,
__typename: 'User',
}]}}});
return updated;
}
}
}),
StopIgnoringUser: ({variables}) => ({
updateQueries: {
CoralEmbedStream_Profile: (previousData) => {
+1 -1
View File
@@ -293,7 +293,7 @@ export const withIgnoreUser = withMutation(
}
`, {
props: ({mutate}) => ({
ignoreUser: ({id}) => {
ignoreUser: (id) => {
return mutate({
variables: {
id,
+1
View File
@@ -5,3 +5,4 @@ export {default as withFragments} from 'coral-framework/hocs/withFragments';
export {default as excludeIf} from 'coral-framework/hocs/excludeIf';
export {default as connect} from 'coral-framework/hocs/connect';
export {default as withEmit} from 'coral-framework/hocs/withEmit';
export {withIgnoreUser} from 'coral-framework/graphql/mutations';
@@ -35,3 +35,7 @@
transform: rotate(180deg);
}
.actions {
display: flex;
justify-content: flex-end;
}
@@ -5,11 +5,13 @@ import {Slot} from 'plugin-api/beta/client/components';
export default ({data, root, asset, comment, contentSlot}) => {
if (contentSlot) {
return (
<Slot
fill={contentSlot}
data={data}
queryData={{asset, root, comment}}
/>
<div className={styles.menu}>
<Slot
fill={contentSlot}
data={data}
queryData={{asset, root, comment}}
/>
</div>
);
}
@@ -21,6 +23,7 @@ export default ({data, root, asset, comment, contentSlot}) => {
queryData={{asset, root, comment}}
/>
<Slot
className={styles.actions}
fill={'authorMenuActions'}
data={data}
queryData={{asset, root, comment}}
@@ -22,6 +22,9 @@ class AuthorNameContainer extends React.Component {
this.setState({
menuVisible: false
});
if (this.props.contentSlot) {
this.props.resetContentSlot();
}
}
}
@@ -31,6 +34,7 @@ class AuthorNameContainer extends React.Component {
root={this.props.root}
asset={this.props.asset}
comment={this.props.comment}
contentSlot={this.props.contentSlot}
menuVisible={this.state.menuVisible}
toggleMenu={this.toggleMenu}
hideMenu={this.hideMenu}
@@ -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"
]
}
@@ -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"] }]
}
}
@@ -0,0 +1,16 @@
.root {
white-space: nowrap;
}
.memberSince {
color: #484747;
margin-left: 4px;
letter-spacing: 0.2px;
font-size: 0.9em;
vertical-align: middle;
}
.icon {
font-size: 1.4em;
vertical-align: middle;
}
@@ -0,0 +1,9 @@
import React from 'react';
import styles from './IgnoreUserAction.css';
import {t} from 'plugin-api/beta/client/services';
export default ({ignoreUser}) => (
<button className={styles.button} onClick={ignoreUser}>
{t('talk-plugin-ignore-user.ignore')}
</button>
);
@@ -0,0 +1,9 @@
.root {
width: 250px;
}
.actions {
display: flex;
justify-content: flex-end;
}
@@ -0,0 +1,14 @@
import React from 'react';
import styles from './IgnoreUserConfirmation.css';
import {t} from 'plugin-api/beta/client/services';
export default ({ignoreUser}) => (
<div className={styles.root}>
Do you really want to ignore this user?
<div className={styles.actions}>
<button className={styles.button} onClick={ignoreUser}>
{t('talk-plugin-ignore-user.ignore')}
</button>
</div>
</div>
);
@@ -0,0 +1,34 @@
import React from 'react';
import IgnoreUserAction from '../components/IgnoreUserAction';
import {compose} from 'react-apollo';
import {connect, withFragments} from 'plugin-api/beta/client/hocs';
import {bindActionCreators} from 'redux';
import {setContentSlot} from 'plugins/talk-plugin-author-menu/client/actions';
import IgnoreUserConfirmation from './IgnoreUserConfirmation';
class IgnoreUserActionContainer extends React.Component {
ignoreUser = () => {
this.props.setContentSlot('ignoreUserConfirmation');
};
render() {
return <IgnoreUserAction
ignoreUser={this.ignoreUser}
/>;
}
}
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
setContentSlot,
}, dispatch);
const withIgnoreUserActionFragments = withFragments(IgnoreUserConfirmation.fragments);
const enhance = compose(
connect(null, mapDispatchToProps),
withIgnoreUserActionFragments,
);
export default enhance(IgnoreUserActionContainer);
@@ -0,0 +1,44 @@
import React from 'react';
import IgnoreUserConfirmation from '../components/IgnoreUserConfirmation';
import {compose, gql} from 'react-apollo';
import {connect, withFragments, withIgnoreUser} from 'plugin-api/beta/client/hocs';
import {bindActionCreators} from 'redux';
import {setContentSlot} from 'plugins/talk-plugin-author-menu/client/actions';
class IgnoreUserConfirmationContainer extends React.Component {
ignoreUser = () => {
this.props.ignoreUser(this.props.comment.user.id);
this.props.setContentSlot(null);
};
render() {
return <IgnoreUserConfirmation
username={this.props.comment.username}
ignoreUser={this.ignoreUser}
/>;
}
}
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
setContentSlot,
}, dispatch);
const withIgnoreUserConfirmationFragments = withFragments({
comment: gql`
fragment TalkIgnoreUser_IgnoreUserConfirmation_comment on Comment {
user {
id
username
}
}`,
});
const enhance = compose(
connect(null, mapDispatchToProps),
withIgnoreUserConfirmationFragments,
withIgnoreUser,
);
export default enhance(IgnoreUserConfirmationContainer);
@@ -0,0 +1,26 @@
import IgnoreUserAction from './containers/IgnoreUserAction';
import IgnoreUserConfirmation from './containers/IgnoreUserConfirmation';
import translations from './translations.yml';
import update from 'immutability-helper';
export default {
mutations: {
IgnoreUser: ({variables}) => ({
updateQueries: {
CoralEmbedStream_Embed: (previousData) => {
const ignoredUserId = variables.id;
const updated = update(previousData, {me: {ignoredUsers: {$push: [{
id: ignoredUserId,
__typename: 'User',
}]}}});
return updated;
}
}
}),
},
slots: {
authorMenuActions: [IgnoreUserAction],
ignoreUserConfirmation: [IgnoreUserConfirmation]
},
translations
};
@@ -0,0 +1,5 @@
en:
talk-plugin-ignore-user:
ignore: "ignore"
es:
talk-plugin-ignore-user:
+1
View File
@@ -0,0 +1 @@
module.exports = {};