mirror of
https://github.com/wassname/talk.git
synced 2026-08-10 12:41:02 +08:00
Adding Coral-Plugin-Like
This commit is contained in:
@@ -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,6 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
|
||||
export default ({className}) => (
|
||||
<i className={cn('fa', 'fa-handshake-o', className)} aria-hidden="true"/>
|
||||
);
|
||||
@@ -0,0 +1,69 @@
|
||||
import React, {Component} from 'react';
|
||||
import styles from './style.css';
|
||||
import Icon from './Icon';
|
||||
|
||||
import {I18n} from 'coral-framework';
|
||||
import cn from 'classnames';
|
||||
import translations from '../translations.json';
|
||||
import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils';
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
class RespectButton extends Component {
|
||||
|
||||
handleClick = () => {
|
||||
const {postRespect, showSignInDialog, deleteAction} = this.props;
|
||||
const {root: {me}, comment} = this.props;
|
||||
|
||||
const myRespectActionSummary = getMyActionSummary('RespectActionSummary', comment);
|
||||
|
||||
// If the current user does not exist, trigger sign in dialog.
|
||||
if (!me) {
|
||||
showSignInDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the current user is banned, do nothing.
|
||||
if (me.status === 'BANNED') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (myRespectActionSummary) {
|
||||
deleteAction(myRespectActionSummary.current_user.id, comment.id);
|
||||
} else {
|
||||
postRespect({
|
||||
item_id: comment.id,
|
||||
item_type: 'COMMENTS'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {comment} = this.props;
|
||||
|
||||
if (!comment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const myRespect = getMyActionSummary('RespectActionSummary', comment);
|
||||
let count = getTotalActionCount('RespectActionSummary', comment);
|
||||
|
||||
return (
|
||||
<div className={styles.respect}>
|
||||
<button
|
||||
className={cn(styles.button, {[styles.respected]: myRespect})}
|
||||
onClick={this.handleClick} >
|
||||
<span>{lang.t(myRespect ? 'respected' : 'respect')}</span>
|
||||
<Icon className={cn(styles.icon, {[styles.respected]: myRespect})} />
|
||||
{count > 0 && count}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RespectButton.propTypes = {
|
||||
data: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default RespectButton;
|
||||
@@ -0,0 +1,30 @@
|
||||
.respect {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.button {
|
||||
color: #2a2a2a;
|
||||
margin: 5px 10px 5px 0px;
|
||||
background: none;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
font-size: inherit;
|
||||
|
||||
&:hover {
|
||||
color: #767676;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.respected {
|
||||
color: #c98211;
|
||||
|
||||
&:hover {
|
||||
color: #e59614;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 0 5px;
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import {compose, gql, graphql} from 'react-apollo';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import get from 'lodash/get';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import {showSignInDialog} from 'coral-framework/actions/auth';
|
||||
import RespectButton from '../components/LikeButton';
|
||||
|
||||
const isRespectAction = (a) => a.__typename === 'RespectActionSummary';
|
||||
|
||||
const COMMENT_FRAGMENT = gql`
|
||||
fragment RespectButton_updateFragment on Comment {
|
||||
action_summaries {
|
||||
... on RespectActionSummary {
|
||||
count
|
||||
current_user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const withDeleteAction = graphql(gql`
|
||||
mutation deleteAction($id: ID!) {
|
||||
deleteAction(id:$id) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
deleteAction: (id, commentId) => {
|
||||
return mutate({
|
||||
variables: {id},
|
||||
optimisticResponse: {
|
||||
deleteAction: {
|
||||
__typename: 'DeleteActionResponse',
|
||||
errors: null,
|
||||
}
|
||||
},
|
||||
update: (proxy) => {
|
||||
const fragmentId = `Comment_${commentId}`;
|
||||
|
||||
// Read the data from our cache for this query.
|
||||
const data = proxy.readFragment({fragment: COMMENT_FRAGMENT, id: fragmentId});
|
||||
|
||||
// Check whether we respected this comment.
|
||||
const idx = data.action_summaries.findIndex(isRespectAction);
|
||||
if (idx < 0 || get(data.action_summaries[idx], 'current_user.id') !== id) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.action_summaries[idx] = {
|
||||
...data.action_summaries[idx],
|
||||
count: data.action_summaries[idx].count - 1,
|
||||
current_user: null,
|
||||
};
|
||||
|
||||
// Write our data back to the cache.
|
||||
proxy.writeFragment({fragment: COMMENT_FRAGMENT, id: fragmentId, data});
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const withPostRespect = graphql(gql`
|
||||
mutation createRespect($respect: CreateRespectInput!) {
|
||||
createRespect(respect: $respect) {
|
||||
respect {
|
||||
id
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
postRespect: (respect) => {
|
||||
return mutate({
|
||||
variables: {respect},
|
||||
optimisticResponse: {
|
||||
createRespect: {
|
||||
__typename: 'CreateRespectResponse',
|
||||
errors: null,
|
||||
respect: {
|
||||
__typename: 'RespectAction',
|
||||
id: 'pending',
|
||||
},
|
||||
}
|
||||
},
|
||||
update: (proxy, mutationResult) => {
|
||||
const fragmentId = `Comment_${respect.item_id}`;
|
||||
|
||||
// Read the data from our cache for this query.
|
||||
const data = proxy.readFragment({fragment: COMMENT_FRAGMENT, id: fragmentId});
|
||||
|
||||
// Add our comment from the mutation to the end.
|
||||
let idx = data.action_summaries.findIndex(isRespectAction);
|
||||
|
||||
// Check whether we already respected this comment.
|
||||
if (idx >= 0 && data.action_summaries[idx].current_user) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx < 0) {
|
||||
|
||||
// Add initial action when it doesn't exist.
|
||||
data.action_summaries.push({
|
||||
__typename: 'RespectActionSummary',
|
||||
count: 0,
|
||||
current_user: null,
|
||||
});
|
||||
idx = data.action_summaries.length - 1;
|
||||
}
|
||||
|
||||
data.action_summaries[idx] = {
|
||||
...data.action_summaries[idx],
|
||||
count: data.action_summaries[idx].count + 1,
|
||||
current_user: mutationResult.data.createRespect.respect,
|
||||
};
|
||||
|
||||
// Write our data back to the cache.
|
||||
proxy.writeFragment({fragment: COMMENT_FRAGMENT, id: fragmentId, data});
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({showSignInDialog}, dispatch);
|
||||
|
||||
const enhance = compose(
|
||||
withFragments({
|
||||
root: gql`
|
||||
fragment RespectButton_root on RootQuery {
|
||||
me {
|
||||
status
|
||||
}
|
||||
}
|
||||
`,
|
||||
comment: gql`
|
||||
fragment RespectButton_comment on Comment {
|
||||
action_summaries {
|
||||
... on RespectActionSummary {
|
||||
count
|
||||
current_user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}),
|
||||
connect(null, mapDispatchToProps),
|
||||
withDeleteAction,
|
||||
withPostRespect,
|
||||
);
|
||||
|
||||
export default enhance(RespectButton);
|
||||
@@ -0,0 +1,7 @@
|
||||
import LikeButton from './containers/LikeButton';
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
commentDetail: [LikeButton],
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"en": {
|
||||
"respect": "Respect",
|
||||
"respected": "Respected"
|
||||
},
|
||||
"es": {
|
||||
"respect": "Respeto",
|
||||
"respected": "Respetado"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
const {readFileSync} = require('fs');
|
||||
const path = require('path');
|
||||
const wrapResponse = require('../../graph/helpers/response');
|
||||
|
||||
module.exports = {
|
||||
typeDefs: readFileSync(path.join(__dirname, 'server/typeDefs.graphql'), 'utf8'),
|
||||
resolvers: {
|
||||
RootMutation: {
|
||||
createRespect(_, {like: {item_id, item_type}}, {mutators: {Action}}) {
|
||||
return wrapResponse('like')(Action.create({item_id, item_type, action_type: 'LIKE'}));
|
||||
}
|
||||
}
|
||||
},
|
||||
hooks: {
|
||||
Action: {
|
||||
__resolveType: {
|
||||
post({action_type}) {
|
||||
switch (action_type) {
|
||||
case 'LIKE':
|
||||
return 'LikeAction';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ActionSummary: {
|
||||
__resolveType: {
|
||||
post({action_type}) {
|
||||
switch (action_type) {
|
||||
case 'LIKE':
|
||||
return 'LikeActionSummary';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
enum ACTION_TYPE {
|
||||
|
||||
# Represents a Like.
|
||||
LIKE
|
||||
}
|
||||
|
||||
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!
|
||||
}
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
type LikeActionSummary implements ActionSummary {
|
||||
|
||||
# The count of actions with this group.
|
||||
count: Int
|
||||
|
||||
# The current user's action.
|
||||
current_user: LikeAction
|
||||
}
|
||||
|
||||
type CreateLikeResponse implements Response {
|
||||
|
||||
# The like that was created.
|
||||
like: LikeAction
|
||||
|
||||
# An array of errors relating to the mutation that occurred.
|
||||
errors: [UserError]
|
||||
}
|
||||
|
||||
type RootMutation {
|
||||
|
||||
# Creates a like on an entity.
|
||||
createLike(like: CreateLikeInput!): CreateLikeResponse
|
||||
}
|
||||
Reference in New Issue
Block a user