mirror of
https://github.com/wassname/talk.git
synced 2026-07-09 08:00:17 +08:00
Almost fully working
This commit is contained in:
@@ -21,7 +21,7 @@ export function getSlotElements(slot, props = {}) {
|
||||
.filter(o => o.module.slots[slot])
|
||||
.map(o => o.module.slots[slot]));
|
||||
return components
|
||||
.map((component, i) => React.createElement(component, {...props, key: i}));
|
||||
.map((component, i) => React.createElement(component, {key: i, ...props}));
|
||||
}
|
||||
|
||||
function getComponentFragments(components) {
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {compose, gql, graphql} from 'react-apollo';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import {showSignInDialog} from 'coral-framework/actions/auth';
|
||||
import {capitalize} from 'coral-framework/helpers/strings';
|
||||
import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils';
|
||||
|
||||
export default reaction => WrappedComponent => {
|
||||
if (typeof reaction !== 'string') {
|
||||
console.error('Reaction must be a valid string');
|
||||
return null;
|
||||
}
|
||||
|
||||
reaction = reaction.toLowerCase();
|
||||
|
||||
class WithReactions extends React.Component {
|
||||
render() {
|
||||
const {comment} = this.props;
|
||||
|
||||
const reactionSummary = getMyActionSummary(
|
||||
`${capitalize(reaction)}ActionSummary`,
|
||||
comment
|
||||
);
|
||||
const count = getTotalActionCount(
|
||||
`${capitalize(reaction)}ActionSummary`,
|
||||
comment
|
||||
);
|
||||
|
||||
const withReactionProps = {reactionSummary, count};
|
||||
|
||||
return <WrappedComponent {...this.props} {...withReactionProps} />;
|
||||
}
|
||||
}
|
||||
|
||||
const isReaction = a =>
|
||||
a.__typereaction === `${capitalize(reaction)}ActionSummary`;
|
||||
|
||||
const COMMENT_FRAGMENT = gql`
|
||||
fragment ${capitalize(reaction)}Button_updateFragment on Comment {
|
||||
action_summaries {
|
||||
... on ${capitalize(reaction)}ActionSummary {
|
||||
count
|
||||
current_user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const withDeleteReaction = graphql(
|
||||
gql`
|
||||
mutation deleteReaction($id: ID!) {
|
||||
deleteAction(id:$id) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({mutate}) => ({
|
||||
deleteReaction: (id, commentId) => {
|
||||
return mutate({
|
||||
variables: {id},
|
||||
optimisticResponse: {
|
||||
deleteAction: {
|
||||
__typereaction: '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 liked this comment.
|
||||
const idx = data.action_summaries.findIndex(isReaction);
|
||||
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 withPostReaction = graphql(
|
||||
gql`
|
||||
mutation create${capitalize(reaction)}($${reaction}: Create${capitalize(reaction)}Input!) {
|
||||
create${capitalize(reaction)}(${reaction}: $${reaction}) {
|
||||
${reaction} {
|
||||
id
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({mutate}) => ({
|
||||
postReaction: reactionData => {
|
||||
return mutate({
|
||||
variables: {[reaction]: reactionData},
|
||||
optimisticResponse: {
|
||||
[`create${capitalize(reaction)}`]: {
|
||||
__typereaction: `Create${capitalize(reaction)}Response`,
|
||||
errors: null,
|
||||
[reaction]: {
|
||||
__typereaction: `${capitalize(reaction)}Action`,
|
||||
id: 'pending'
|
||||
}
|
||||
}
|
||||
},
|
||||
update: (proxy, mutationResult) => {
|
||||
console.log(isReaction);
|
||||
const fragmentId = `Comment_${reactionData.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(isReaction);
|
||||
console.log(data.action_summaries);
|
||||
|
||||
// Check whether we already reactioned 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({
|
||||
__typereaction: `${capitalize(reaction)}ActionSummary`,
|
||||
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[
|
||||
`create${capitalize(reaction)}`
|
||||
][reaction]
|
||||
};
|
||||
|
||||
// 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 ${capitalize(reaction)}Button_root on RootQuery {
|
||||
me {
|
||||
status
|
||||
}
|
||||
}
|
||||
`,
|
||||
comment: gql`
|
||||
fragment ${capitalize(reaction)}Button_comment on Comment {
|
||||
action_summaries {
|
||||
... on ${capitalize(reaction)}ActionSummary {
|
||||
count
|
||||
current_user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
}),
|
||||
connect(null, mapDispatchToProps),
|
||||
withDeleteReaction,
|
||||
withPostReaction
|
||||
);
|
||||
|
||||
return enhance(WithReactions);
|
||||
};
|
||||
@@ -1,98 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import styles from './style.css';
|
||||
|
||||
import {capitalize} from '../helpers';
|
||||
import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils';
|
||||
|
||||
class CoralReaction extends React.Component {
|
||||
handleClick = () => {
|
||||
const name = this.props.children;
|
||||
const {postReaction, deleteReaction, showSignInDialog} = this.props;
|
||||
|
||||
const {root: {me}, comment} = this.props;
|
||||
|
||||
const myActionSummary = getMyActionSummary(
|
||||
`${capitalize(name)}ActionSummary`,
|
||||
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 (myActionSummary) {
|
||||
deleteReaction(myActionSummary.current_user.id, comment.id);
|
||||
} else {
|
||||
postReaction({
|
||||
item_id: comment.id,
|
||||
item_type: 'COMMENTS'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const name = this.props.children;
|
||||
const {comment} = this.props;
|
||||
|
||||
console.log(this.props)
|
||||
|
||||
if (!comment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const myReaction = getMyActionSummary(`${capitalize(name)}ActionSummary`, comment);
|
||||
let count = getTotalActionCount(`${capitalize(name)}ActionSummary`, comment);
|
||||
|
||||
return (
|
||||
<div className={cn(styles.like, `${name}-container`)}>
|
||||
<button
|
||||
className={cn(
|
||||
styles.button,
|
||||
{[styles.liked]: myReaction},
|
||||
`${name}-button`
|
||||
)}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<span className={`${name}-button-text`}>
|
||||
{this.props.children} {(myReaction ? 'd' : '')}
|
||||
</span>
|
||||
<i
|
||||
className={cn(
|
||||
styles.icon,
|
||||
'material-icons',
|
||||
{[styles.liked]: myReaction},
|
||||
`${name}-icon`
|
||||
)}
|
||||
aria-hidden={true}
|
||||
>
|
||||
thumb_up
|
||||
</i>
|
||||
<span className={`${name}-count`}>{count > 0 && count}</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CoralReaction.propTypes = {
|
||||
children: PropTypes.string.isRequired,
|
||||
icon: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||
tag: PropTypes.string,
|
||||
translations: PropTypes.object
|
||||
};
|
||||
|
||||
export default CoralReaction;
|
||||
|
||||
/**
|
||||
*
|
||||
* icon: Could be a string or a component https://material.io/icons/
|
||||
**/
|
||||
@@ -1,30 +0,0 @@
|
||||
.like {
|
||||
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;
|
||||
}
|
||||
|
||||
&.liked {
|
||||
color: rgb(0,134,227);
|
||||
|
||||
&:hover {
|
||||
color: rgb(0,134,227);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 0 5px;
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
import get from 'lodash/get';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {compose, gql, graphql} from 'react-apollo';
|
||||
import CoralReaction from '../components/CoralReaction';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import {showSignInDialog} from 'coral-framework/actions/auth';
|
||||
import {capitalize} from '../helpers';
|
||||
|
||||
const name = 'love';
|
||||
|
||||
const isReaction = a => a.__typename === `${capitalize(name)}ActionSummary`;
|
||||
|
||||
const COMMENT_FRAGMENT = gql`
|
||||
fragment ${capitalize(name)}Button_updateFragment on Comment {
|
||||
action_summaries {
|
||||
... on ${capitalize(name)}ActionSummary {
|
||||
count
|
||||
current_user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const withDeleteReaction = graphql(
|
||||
gql`
|
||||
mutation deleteReaction($id: ID!) {
|
||||
deleteAction(id:$id) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({mutate}) => ({
|
||||
deleteReaction: (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 liked this comment.
|
||||
const idx = data.action_summaries.findIndex(isReaction);
|
||||
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 withPostReaction = graphql(
|
||||
gql`
|
||||
mutation create${capitalize(name)}($${name}: Create${capitalize(name)}Input!) {
|
||||
create${capitalize(name)}(${name}: $${capitalize(name)}) {
|
||||
${name} {
|
||||
id
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({mutate}) => ({
|
||||
postReaction: reaction => {
|
||||
return mutate({
|
||||
variables: {reaction},
|
||||
optimisticResponse: {
|
||||
[`create${capitalize(name)}`]: {
|
||||
__typename: `Create${capitalize(name)}Response`,
|
||||
errors: null,
|
||||
[name]: {
|
||||
__typename: `${capitalize(name)}Action`,
|
||||
id: 'pending'
|
||||
}
|
||||
}
|
||||
},
|
||||
update: (proxy, mutationResult) => {
|
||||
const fragmentId = `Comment_${reaction.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(isReaction);
|
||||
|
||||
// Check whether we already reactioned 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: `${capitalize(name)}ActionSummary`,
|
||||
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[`create${capitalize(name)}`][name]
|
||||
};
|
||||
|
||||
// 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 ${capitalize(name)}Button_root on RootQuery {
|
||||
me {
|
||||
status
|
||||
}
|
||||
}
|
||||
`,
|
||||
comment: gql`
|
||||
fragment ${capitalize(name)}Button_comment on Comment {
|
||||
action_summaries {
|
||||
... on ${capitalize(name)}ActionSummary {
|
||||
count
|
||||
current_user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
}),
|
||||
connect(null, mapDispatchToProps),
|
||||
withDeleteReaction,
|
||||
withPostReaction
|
||||
);
|
||||
|
||||
export default enhance(CoralReaction);
|
||||
@@ -1,3 +0,0 @@
|
||||
import CoralReaction from './containers/CoralReaction';
|
||||
|
||||
export default CoralReaction;
|
||||
@@ -1,80 +0,0 @@
|
||||
export function typeDefGenerator(type = '') {
|
||||
function capitalize(str) {
|
||||
const newString = String.new(str);
|
||||
return newString.charAt(0).toUpperCase() + newString.slice(1);
|
||||
}
|
||||
|
||||
return `
|
||||
enum ACTION_TYPE {
|
||||
|
||||
# Represents a Like.
|
||||
LIKE
|
||||
}
|
||||
|
||||
enum ASSET_METRICS_SORT {
|
||||
|
||||
# Represents a LikeAction.
|
||||
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
|
||||
}
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
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