diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js
index 20aee7abb..d701b933f 100644
--- a/client/coral-framework/helpers/plugins.js
+++ b/client/coral-framework/helpers/plugins.js
@@ -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) {
diff --git a/client/coral-reaction/helpers.js b/client/coral-framework/helpers/strings.js
similarity index 100%
rename from client/coral-reaction/helpers.js
rename to client/coral-framework/helpers/strings.js
diff --git a/client/coral-framework/hocs/withReaction.js b/client/coral-framework/hocs/withReaction.js
new file mode 100644
index 000000000..b838941ad
--- /dev/null
+++ b/client/coral-framework/hocs/withReaction.js
@@ -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 ;
+ }
+ }
+
+ 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);
+};
diff --git a/client/coral-reaction/actions.js b/client/coral-reaction/actions.js
deleted file mode 100644
index e69de29bb..000000000
diff --git a/client/coral-reaction/components/CoralReaction.js b/client/coral-reaction/components/CoralReaction.js
deleted file mode 100644
index f691e2bb7..000000000
--- a/client/coral-reaction/components/CoralReaction.js
+++ /dev/null
@@ -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 (
-
-
-
- );
- }
-}
-
-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/
- **/
diff --git a/client/coral-reaction/components/style.css b/client/coral-reaction/components/style.css
deleted file mode 100644
index a82f20ff6..000000000
--- a/client/coral-reaction/components/style.css
+++ /dev/null
@@ -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;
-}
diff --git a/client/coral-reaction/constants.js b/client/coral-reaction/constants.js
deleted file mode 100644
index e69de29bb..000000000
diff --git a/client/coral-reaction/containers/CoralReaction.js b/client/coral-reaction/containers/CoralReaction.js
deleted file mode 100644
index f8880a935..000000000
--- a/client/coral-reaction/containers/CoralReaction.js
+++ /dev/null
@@ -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);
diff --git a/client/coral-reaction/index.js b/client/coral-reaction/index.js
deleted file mode 100644
index d8527b9fd..000000000
--- a/client/coral-reaction/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import CoralReaction from './containers/CoralReaction';
-
-export default CoralReaction;
\ No newline at end of file
diff --git a/client/coral-reaction/reducer.js b/client/coral-reaction/reducer.js
deleted file mode 100644
index e69de29bb..000000000
diff --git a/client/coral-reaction/typeDefGenerator.js b/client/coral-reaction/typeDefGenerator.js
deleted file mode 100644
index 63c20684e..000000000
--- a/client/coral-reaction/typeDefGenerator.js
+++ /dev/null
@@ -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
-}
-
-`;
-}
\ No newline at end of file