Merge branch 'master' into asset-improvements

This commit is contained in:
Belén Curcio
2017-06-12 16:19:23 -03:00
committed by GitHub
39 changed files with 792 additions and 1187 deletions
+2 -2
View File
@@ -311,11 +311,11 @@ export const checkLogin = () => (dispatch) => {
throw new Error('Not logged in');
}
dispatch(checkLoginSuccess(result.user));
// Reset the websocket.
resetWebsocket();
dispatch(checkLoginSuccess(result.user));
// Display create username dialog if necessary.
if (result.user.canEditName && result.user.status !== 'BANNED') {
dispatch(showCreateUsernameDialog());
-1
View File
@@ -1,5 +1,4 @@
export {default as withFragments} from './withFragments';
export {default as withMutation} from './withMutation';
export {default as withQuery} from './withQuery';
export {default as withReaction} from './withReaction';
-240
View File
@@ -1,240 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import uuid from 'uuid/v4';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getDisplayName} from '../helpers/hoc';
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 alreadyReacted = () => !!reactionSummary;
const withReactionProps = {reactionSummary, count, alreadyReacted};
return <WrappedComponent {...this.props} {...withReactionProps} />;
}
}
const isReaction = (a) =>
a.__typename === `${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, ownProps}) => ({
deleteReaction: () => {
const reactionSummary = getMyActionSummary(
`${capitalize(reaction)}ActionSummary`,
ownProps.comment
);
const reactionData = {
id: reactionSummary.current_user.id,
commentId: ownProps.comment.id
};
return mutate({
variables: {id: reactionData.id},
optimisticResponse: {
deleteAction: {
__typename: 'DeleteActionResponse',
errors: null
}
},
update: (proxy) => {
const fragmentId = `Comment_${reactionData.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') !== reactionData.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, ownProps}) => ({
postReaction: () => {
const reactionData = {
item_id: ownProps.comment.id,
item_type: 'COMMENTS'
};
return mutate({
variables: {[reaction]: reactionData},
optimisticResponse: {
[`create${capitalize(reaction)}`]: {
__typename: `Create${capitalize(reaction)}Response`,
errors: null,
[reaction]: {
__typename: `${capitalize(reaction)}Action`,
id: uuid()
}
}
},
update: (proxy, mutationResult) => {
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);
// 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(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 mapStateToProps = (state) => ({
user: state.auth.toJS().user,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({showSignInDialog}, dispatch);
const enhance = compose(
withFragments({
comment: gql`
fragment ${capitalize(reaction)}Button_comment on Comment {
action_summaries {
... on ${capitalize(reaction)}ActionSummary {
count
current_user {
id
}
}
}
}`
}),
connect(mapStateToProps, mapDispatchToProps),
withDeleteReaction,
withPostReaction
);
WithReactions.displayName = `WithReactions(${getDisplayName(WrappedComponent)})`;
return enhance(WithReactions);
};
+12 -12
View File
@@ -1,7 +1,7 @@
import ApolloClient, {addTypename} from 'apollo-client';
import {networkInterface} from './transport';
import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws';
import {SUBSCRIPTION_END} from 'subscriptions-transport-ws/dist/messageTypes';
import MessageTypes from 'subscriptions-transport-ws/dist/message-types';
import {getAuthToken} from '../helpers/request';
let client, wsClient = null, wsClientToken = null;
@@ -13,18 +13,17 @@ export function resetWebsocket() {
return;
}
// Unsubscribe from all the active subscriptions.
Object.keys(wsClient.subscriptions).forEach((id) => {
// Create the message.
let message = {id: parseInt(id), type: SUBSCRIPTION_END};
// Send the unsubscribe message.
wsClient.client.send(JSON.stringify(message));
});
// Close the client, this will trigger a reconnect.
// Close socket connection which will also unregister subscriptions on the server-side.
wsClient.close();
// Reconnect to the server.
wsClient.connect();
// Reregister all subscriptions (uses non public api).
// See: https://github.com/apollographql/subscriptions-transport-ws/issues/171
Object.keys(wsClient.operations).forEach((id) => {
wsClient.sendMessage(id, MessageTypes.GQL_START, wsClient.operations[id].options);
});
}
export function getClient() {
@@ -35,6 +34,7 @@ export function getClient() {
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
wsClient = new SubscriptionClient(`${protocol}://${location.host}/api/v1/live`, {
reconnect: true,
lazy: true,
connectionParams: {
get token() {
+1 -1
View File
@@ -39,7 +39,7 @@ const createAction = async ({user = {}}, {item_id, item_type, action_type, group
* @return {Promise} resolves when the action is deleted
*/
const deleteAction = ({user}, {id}) => {
return ActionModel.remove({
return ActionModel.findOneAndRemove({
id,
user_id: user.id
});
+3 -3
View File
@@ -50,7 +50,7 @@ const createSubscriptionManager = (server) => new SubscriptionServer({
connection.upgradeReq.headers['authorization'] = `Bearer ${token}`;
}
},
onSubscribe: (parsedMessage, baseParams, connection) => {
onOperation: (parsedMessage, baseParams, connection) => {
// Cache the upgrade request.
let upgradeReq = connection.upgradeReq;
@@ -58,7 +58,7 @@ const createSubscriptionManager = (server) => new SubscriptionServer({
// Attach the context per request.
baseParams.context = async () => {
let req;
try {
req = await deserializeUser(upgradeReq);
} catch (e) {
@@ -66,7 +66,7 @@ const createSubscriptionManager = (server) => new SubscriptionServer({
return new Context({}, pubsub);
}
return new Context(req, pubsub);
};
+2 -2
View File
@@ -81,7 +81,7 @@
"graphql-errors": "^2.1.0",
"graphql-redis-subscriptions": "^1.1.5",
"graphql-server-express": "^0.6.0",
"graphql-subscriptions": "^0.3.1",
"graphql-subscriptions": "^0.4.3",
"graphql-tools": "^0.10.1",
"helmet": "^3.5.0",
"immutability-helper": "^2.2.0",
@@ -116,7 +116,7 @@
"semver": "^5.3.0",
"simplemde": "^1.11.2",
"snake-case": "^2.1.0",
"subscriptions-transport-ws": "^0.5.5-alpha.0",
"subscriptions-transport-ws": "^0.7.2",
"timekeeper": "^1.0.0",
"uuid": "^3.0.1",
"yaml-loader": "^0.4.0",
+1 -1
View File
@@ -1 +1 @@
export {withReaction} from 'coral-framework/hocs';
export {default as withReaction} from './withReaction';
+347
View File
@@ -0,0 +1,347 @@
import React from 'react';
import get from 'lodash/get';
import uuid from 'uuid/v4';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getDisplayName} from 'coral-framework/helpers/hoc';
import {compose, gql} from 'react-apollo';
import withFragments from 'coral-framework/hocs/withFragments';
import withMutation from 'coral-framework/hocs/withMutation';
import {showSignInDialog} from 'coral-framework/actions/auth';
import {capitalize} from 'coral-framework/helpers/strings';
import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils';
import * as PropTypes from 'prop-types';
export default (reaction) => (WrappedComponent) => {
if (typeof reaction !== 'string') {
console.error('Reaction must be a valid string');
return null;
}
// Global instance counter for each `reaction` type.
let instances = 0;
// Track current subscriptions.
let createdSubscription = null;
let deletedSubscription = null;
reaction = reaction.toLowerCase();
const Reaction = capitalize(reaction);
const COMMENT_FRAGMENT = gql`
fragment ${Reaction}Button_updateFragment on Comment {
action_summaries {
... on ${Reaction}ActionSummary {
count
current_user {
id
}
}
}
}
`;
const isReaction = (a) =>
a.__typename === `${Reaction}ActionSummary`;
const addReactionToStore = (proxy, {action, self}) => {
const fragmentId = `Comment_${action.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 (self && 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: `${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: self ? action : data.action_summaries[idx].current_user
};
// Write our data back to the cache.
proxy.writeFragment({
fragment: COMMENT_FRAGMENT,
id: fragmentId,
data
});
};
const deleteReactionFromStore = (proxy, {action, self}) => {
const fragmentId = `Comment_${action.item_id}`;
// 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 (
self &&
(idx < 0 || get(data.action_summaries[idx], 'current_user.id') !== action.id)
) {
return;
}
data.action_summaries[idx] = {
...data.action_summaries[idx],
count: data.action_summaries[idx].count - 1,
current_user: self ? null : data.action_summaries[idx].current_user,
};
// Write our data back to the cache.
proxy.writeFragment({
fragment: COMMENT_FRAGMENT,
id: fragmentId,
data
});
};
const REACTION_CREATED_SUBSCRIPTION = gql`
subscription ${Reaction}ActionCreated($assetId: ID!) {
${reaction}ActionCreated(asset_id: $assetId) {
id
user {
id
}
item_id
}
}
`;
const REACTION_DELETED_SUBSCRIPTION = gql`
subscription ${Reaction}ActionDeleted($assetId: ID!) {
${reaction}ActionDeleted(asset_id: $assetId) {
id
user {
id
}
item_id
}
}
`;
class WithReactions extends React.Component {
static contextTypes = {
client: PropTypes.object.isRequired,
};
constructor(props, context) {
super(props, context);
// Start subscriptions when it is first needed.
if (instances === 0) {
createdSubscription = context.client.subscribe({
query: REACTION_CREATED_SUBSCRIPTION,
variables: {
assetId: this.props.root.asset.id,
},
}).subscribe({
next: this.onReactionCreated,
error(err) { console.error('err', err); },
});
deletedSubscription = context.client.subscribe({
query: REACTION_DELETED_SUBSCRIPTION,
variables: {
assetId: this.props.root.asset.id,
},
}).subscribe({
next: this.onReactionDeleted,
error(err) { console.error('err', err); },
});
}
instances++;
}
// onReactionCreated handles live updates through the subscriptions.
onReactionCreated = ({[`${reaction}ActionCreated`]: action}) => {
if (this.props.user && action.user && this.props.user.id === action.user.id) {
return;
}
addReactionToStore(this.context.client, {action, self: false});
};
// onReactionDeleted handles live updates through the subscriptions.
onReactionDeleted = ({[`${reaction}ActionDeleted`]: action}) => {
if (this.props.user && action.user && this.props.user.id === action.user.id) {
return;
}
deleteReactionFromStore(this.context.client, {action, self: false});
};
componentWillUnmount() {
instances--;
// End subscriptions when last component will be unmounted.
if (instances === 0) {
try {
createdSubscription.unsubscribe();
deletedSubscription.unsubscribe();
}
catch(e) {
console.warn(e);
}
}
}
render() {
const {comment} = this.props;
const reactionSummary = getMyActionSummary(
`${Reaction}ActionSummary`,
comment
);
const count = getTotalActionCount(
`${Reaction}ActionSummary`,
comment
);
const alreadyReacted = !!reactionSummary;
const withReactionProps = {reactionSummary, count, alreadyReacted};
return <WrappedComponent {...this.props} {...withReactionProps} />;
}
}
const withDeleteReaction = withMutation(
gql`
mutation Delete${Reaction}Action($input: Delete${Reaction}ActionInput!) {
delete${Reaction}Action(input: $input) {
errors {
translation_key
}
}
}
`,
{
props: ({mutate, ownProps}) => ({
deleteReaction: () => {
const reactionSummary = getMyActionSummary(
`${Reaction}ActionSummary`,
ownProps.comment
);
const id = reactionSummary.current_user.id;
const item_id = ownProps.comment.id;
const input = {id};
return mutate({
variables: {input},
optimisticResponse: {
[`delete${Reaction}Action`]: {
__typename: `Delete${Reaction}ActionResponse`,
errors: null
}
},
update: (proxy) => {
deleteReactionFromStore(proxy, {action: {item_id, id}, self: true});
}
});
}
})
}
);
const withPostReaction = withMutation(
gql`
mutation Create${Reaction}Action($input: Create${Reaction}ActionInput!) {
create${Reaction}Action(input: $input) {
${reaction} {
id
}
errors {
translation_key
}
}
}
`,
{
props: ({mutate, ownProps}) => ({
postReaction: () => {
const input = {
item_id: ownProps.comment.id,
};
return mutate({
variables: {input},
optimisticResponse: {
[`create${Reaction}Action`]: {
__typename: `Create${Reaction}ActionResponse`,
errors: null,
[reaction]: {
__typename: `${Reaction}Action`,
id: uuid()
}
}
},
update: (proxy, {data: {[`create${Reaction}Action`]: {[reaction]: action}}}) => {
const a = {
...action,
item_id: input.item_id,
};
addReactionToStore(proxy, {action: a, self: true});
}
});
}
})
}
);
const mapStateToProps = (state) => ({
user: state.auth.toJS().user,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({showSignInDialog}, dispatch);
const enhance = compose(
withFragments({
comment: gql`
fragment ${Reaction}Button_comment on Comment {
action_summaries {
... on ${Reaction}ActionSummary {
count
current_user {
id
}
}
}
}`
}),
connect(mapStateToProps, mapDispatchToProps),
withDeleteReaction,
withPostReaction
);
WithReactions.displayName = `WithReactions(${getDisplayName(WrappedComponent)})`;
return enhance(WithReactions);
};
+192
View File
@@ -0,0 +1,192 @@
const wrapResponse = require('../../../graph/helpers/response');
const {SEARCH_OTHER_USERS} = require('../../../perms/constants');
function getReactionConfig(reaction) {
reaction = reaction.toLowerCase();
const Reaction = reaction.charAt(0).toUpperCase() + reaction.slice(1);
const REACTION = reaction.toUpperCase();
const typeDefs = `
enum ACTION_TYPE {
# Represents a ${Reaction}.
${REACTION}
}
enum ASSET_METRICS_SORT {
# Represents a ${Reaction}Action.
${REACTION}
}
input Create${Reaction}ActionInput {
# The item's id for which we are to create a ${reaction}.
item_id: ID!
}
input Delete${Reaction}ActionInput {
# The item's id for which we are deleting a ${reaction}.
id: ID!
}
# ${Reaction}Action is used by users who "${reaction}" a specific entity.
type ${Reaction}Action 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
# The item's id for which the Action was created.
item_id: ID!
}
type ${Reaction}ActionSummary implements ActionSummary {
# The count of actions with this group.
count: Int
# The current user's action.
current_user: ${Reaction}Action
}
# A summary of counts related to all the ${Reaction}s on an Asset.
type ${Reaction}AssetActionSummary implements AssetActionSummary {
# Number of ${reaction}s associated with actionable types on this this Asset.
actionCount: Int
# Number of unique actionable types that are referenced by the ${reaction}s.
actionableItemCount: Int
}
type Create${Reaction}ActionResponse implements Response {
# The ${reaction} that was created.
${reaction}: ${Reaction}Action
# An array of errors relating to the mutation that occurred.
errors: [UserError!]
}
type Delete${Reaction}ActionResponse implements Response {
# The ${reaction} that was created.
${reaction}: ${Reaction}Action
# An array of errors relating to the mutation that occurred.
errors: [UserError!]
}
type RootMutation {
# Creates a ${reaction} on an entity.
create${Reaction}Action(input: Create${Reaction}ActionInput!): Create${Reaction}ActionResponse
delete${Reaction}Action(input: Delete${Reaction}ActionInput!): Delete${Reaction}ActionResponse
}
type Subscription {
# Subscribe to ${reaction}s.
${reaction}ActionCreated(asset_id: ID!): ${Reaction}Action
# Subscribe to ${reaction} removals.
${reaction}ActionDeleted(asset_id: ID!): ${Reaction}Action
}
`;
return {
typeDefs,
resolvers: {
Subscription: {
[`${reaction}ActionCreated`]: ({action}) => {
return action;
},
[`${reaction}ActionDeleted`]: ({action}) => {
return action;
},
},
[`${Reaction}Action`]: {
// This will load the user for the specific action. We'll limit this to the
// admin users only or the current logged in user.
user({user_id}, _, {loaders: {Users}, user}) {
if (user && (user.can(SEARCH_OTHER_USERS) || user_id === user.id)) {
return Users.getByID.load(user_id);
}
}
},
RootMutation: {
[`create${Reaction}Action`]: (_, {input: {item_id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => {
const response = Comments.get.load(item_id).then((comment) => {
return Action.create({item_id, item_type: 'COMMENTS', action_type: REACTION})
.then((action) => {
// The comment is needed to allow better filtering e.g. by asset_id.
pubsub.publish(`${reaction}ActionCreated`, {action, comment});
return Promise.resolve(action);
});
});
return wrapResponse(reaction)(response);
},
[`delete${Reaction}Action`]: (_, {input: {id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => {
const response = Action.delete({id})
.then((action) => {
return Comments.get.load(action.item_id).then((comment) => {
// The comment is needed to allow better filtering e.g. by asset_id.
pubsub.publish(`${reaction}ActionDeleted`, {action, comment});
return Promise.resolve(action);
});
});
return wrapResponse(reaction)(response);
}
},
},
hooks: {
Action: {
__resolveType: {
post({action_type}) {
switch (action_type) {
case REACTION:
return `${Reaction}Action`;
}
}
}
},
ActionSummary: {
__resolveType: {
post({action_type}) {
switch (action_type) {
case REACTION:
return `${Reaction}ActionSummary`;
}
}
}
}
},
setupFunctions: {
[`${reaction}ActionCreated`]: (options, args) => ({
[`${reaction}ActionCreated`]: {
filter: ({comment}) => comment.asset_id === args.asset_id,
},
}),
[`${reaction}ActionDeleted`]: (options, args) => ({
[`${reaction}ActionDeleted`]: {
filter: ({comment}) => comment.asset_id === args.asset_id,
},
}),
},
};
}
module.exports = getReactionConfig;
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
getReactionConfig: require('./getReactionConfig'),
};
@@ -0,0 +1,55 @@
import React from 'react';
import styles from './styles.css';
import {withReaction} from 'plugin-api/beta/client/hocs';
import {t, can} from 'plugin-api/beta/client/services';
import {Icon} from 'plugin-api/beta/client/components';
import cn from 'classnames';
const plugin = 'coral-plugin-like';
class LikeButton extends React.Component {
handleClick = () => {
const {
postReaction,
deleteReaction,
showSignInDialog,
alreadyReacted,
user,
} = this.props;
// If the current user does not exist, trigger sign in dialog.
if (!user) {
showSignInDialog();
return;
}
// If the current user is suspended, do nothing.
if (!can(user, 'INTERACT_WITH_COMMUNITY')) {
return;
}
if (alreadyReacted) {
deleteReaction();
} else {
postReaction();
}
};
render() {
const {count, alreadyReacted} = this.props;
return (
<div className={cn(styles.container, `${plugin}-container`)}>
<button
className={cn(styles.button, {[styles.liked]: alreadyReacted}, `${plugin}-button`)}
onClick={this.handleClick}
>
<span>{t(alreadyReacted ? 'coral-plugin-like.liked' : 'coral-plugin-like.like')}</span>
<Icon name="thumb_up" className={`${plugin}-icon`} />
<span className={`${plugin}-count`}>{count > 0 && count}</span>
</button>
</div>
);
}
}
export default withReaction('like')(LikeButton);
@@ -1,86 +0,0 @@
import React, {Component} from 'react';
import styles from './style.css';
import cn from 'classnames';
import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils';
import t from 'coral-framework/services/i18n';
const name = 'coral-plugin-like';
class LikeButton extends Component {
handleClick = () => {
const {postLike, showSignInDialog, deleteAction} = this.props;
const {root: {me}, comment} = this.props;
const myLikeActionSummary = getMyActionSummary(
'LikeActionSummary',
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 (myLikeActionSummary) {
deleteAction(myLikeActionSummary.current_user.id, comment.id);
} else {
postLike({
item_id: comment.id,
item_type: 'COMMENTS'
});
}
};
render() {
const {comment} = this.props;
if (!comment) {
return null;
}
const myLike = getMyActionSummary('LikeActionSummary', comment);
let count = getTotalActionCount('LikeActionSummary', comment);
return (
<div className={cn(styles.like, `${name}-container`)}>
<button
className={cn(
styles.button,
{[styles.liked]: myLike},
`${name}-button`
)}
onClick={this.handleClick}
>
<span className={`${name}-button-text`}>
{t(myLike ? 'liked' : 'like')}
</span>
<i
className={cn(
styles.icon,
'material-icons',
{[styles.liked]: myLike},
`${name}-icon`
)}
aria-hidden={true}
>
thumb_up
</i>
<span className={`${name}-count`}>{count > 0 && count}</span>
</button>
</div>
);
}
}
LikeButton.propTypes = {
data: React.PropTypes.object.isRequired
};
export default LikeButton;
@@ -1,186 +0,0 @@
import get from 'lodash/get';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose, gql, graphql} from 'react-apollo';
import LikeButton from '../components/LikeButton';
import withFragments from 'coral-framework/hocs/withFragments';
import{showSignInDialog} from 'coral-framework/actions/auth';
const isLikeAction = (a) => a.__typename === 'LikeActionSummary';
const COMMENT_FRAGMENT = gql`
fragment LikeButton_updateFragment on Comment {
action_summaries {
... on LikeActionSummary {
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 liked this comment.
const idx = data.action_summaries.findIndex(isLikeAction);
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 withPostLike = graphql(
gql`
mutation createLike($like: CreateLikeInput!) {
createLike(like: $like) {
like {
id
}
errors {
translation_key
}
}
}
`,
{
props: ({mutate}) => ({
postLike: (like) => {
return mutate({
variables: {like},
optimisticResponse: {
createLike: {
__typename: 'CreateLikeResponse',
errors: null,
like: {
__typename: 'LikeAction',
id: 'pending'
}
}
},
update: (proxy, mutationResult) => {
const fragmentId = `Comment_${like.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(isLikeAction);
// Check whether we already liked 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: 'LikeActionSummary',
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.createLike.like
};
// 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 LikeButton_root on RootQuery {
me {
status
}
}
`,
comment: gql`
fragment LikeButton_comment on Comment {
action_summaries {
... on LikeActionSummary {
count
current_user {
id
}
}
}
}`
}),
connect(null, mapDispatchToProps),
withDeleteAction,
withPostLike
);
export default enhance(LikeButton);
+2 -2
View File
@@ -1,5 +1,5 @@
import LikeButton from './containers/LikeButton';
import translations from './translations.json';
import LikeButton from './LikeButton';
import translations from './translations.yml';
export default {
translations,
@@ -1,6 +1,6 @@
.like {
.container {
display: inline-block;
}
}
.button {
color: #2a2a2a;
@@ -17,14 +17,9 @@
&.liked {
color: rgb(0,134,227);
&:hover {
color: rgb(0,134,227);
cursor: pointer;
}
}
}
.icon {
padding: 0 5px;
}
@@ -1,10 +0,0 @@
{
"en": {
"like": "Like",
"liked": "Liked"
},
"es": {
"like": "Me Gusta",
"liked": "Me Gustó"
}
}
@@ -0,0 +1,9 @@
en:
coral-plugin-like:
like: Like
liked: Liked
es:
coral-plugin-like:
like: Me Gusta
liked: Me Gustó
+2 -36
View File
@@ -1,36 +1,2 @@
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: {
createLike(_, {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';
}
}
}
}
}
};
const {getReactionConfig} = require('../../plugin-api/beta/server');
module.exports = getReactionConfig('like');
@@ -1,70 +0,0 @@
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
}
+15 -10
View File
@@ -1,8 +1,11 @@
import React from 'react';
import {Icon} from 'coral-ui';
import styles from './styles.css';
import {withReaction} from 'plugin-api/beta/client/hocs';
import {t, can} from 'plugin-api/beta/client/services';
import {Icon} from 'plugin-api/beta/client/components';
import cn from 'classnames';
const plugin = 'coral-plugin-love';
class LoveButton extends React.Component {
handleClick = () => {
@@ -25,7 +28,7 @@ class LoveButton extends React.Component {
return;
}
if (alreadyReacted()) {
if (alreadyReacted) {
deleteReaction();
} else {
postReaction();
@@ -35,14 +38,16 @@ class LoveButton extends React.Component {
render() {
const {count, alreadyReacted} = this.props;
return (
<button
className={`${styles.button} ${alreadyReacted() ? styles.loved : ''}`}
onClick={this.handleClick}
>
<span>{t(alreadyReacted() ? 'loved' : 'love')}</span>
<Icon name="favorite" />
<span>{count > 0 && count}</span>
</button>
<div className={cn(styles.container, `${plugin}-container`)}>
<button
className={cn(styles.button, {[styles.loved]: alreadyReacted}, `${plugin}-button`)}
onClick={this.handleClick}
>
<span>{t(alreadyReacted ? 'coral-plugin-love.loved' : 'coral-plugin-love.love')}</span>
<Icon name="favorite" className={`${plugin}-icon`} />
<span className={`${plugin}-count`}>{count > 0 && count}</span>
</button>
</div>
);
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import LoveButton from './LoveButton';
import translations from './translations.json';
import translations from './translations.yml';
export default {
translations,
+18 -19
View File
@@ -1,26 +1,25 @@
.respect {
display: inline-block;
.container {
display: inline-block;
}
.button {
color: #2a2a2a;
margin: 5px 10px 5px 0px;
background: none;
padding: 0px;
border: none;
font-size: inherit;
color: #2a2a2a;
margin: 5px 10px 5px 0px;
background: none;
padding: 0px;
border: none;
font-size: inherit;
&:hover {
color: #767676;
cursor: pointer;
}
&.loved {
color: #e52338;
&:hover {
color: #767676;
cursor: pointer;
}
&.loved {
color: #e52338;
&:hover {
color: #e52839;
cursor: pointer;
}
color: #e52839;
cursor: pointer;
}
}
}
@@ -1,10 +0,0 @@
{
"en": {
"love": "Love",
"loved": "Loved"
},
"es": {
"love": "Amo",
"loved": "Amé"
}
}
@@ -0,0 +1,9 @@
en:
coral-plugin-love:
love: Love
loved: Loved
es:
coral-plugin-love:
love: Amo
loved: Amé
+2 -36
View File
@@ -1,36 +1,2 @@
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: {
createLove(_, {love: {item_id, item_type}}, {mutators: {Action}}) {
return wrapResponse('love')(Action.create({item_id, item_type, action_type: 'LOVE'}));
}
}
},
hooks: {
Action: {
__resolveType: {
post({action_type}) {
switch (action_type) {
case 'LOVE':
return 'LoveAction';
}
}
}
},
ActionSummary: {
__resolveType: {
post({action_type}) {
switch (action_type) {
case 'LOVE':
return 'LoveActionSummary';
}
}
}
}
}
};
const {getReactionConfig} = require('../../plugin-api/beta/server');
module.exports = getReactionConfig('love');
@@ -1,70 +0,0 @@
enum ACTION_TYPE {
# Represents a Love.
LOVE
}
enum ASSET_METRICS_SORT {
# Represents a LoveAction.
LOVE
}
input CreateLoveInput {
# The item's id for which we are to create a love.
item_id: ID!
# The type of the item for which we are to create the love.
item_type: ACTION_ITEM_TYPE!
}
# LoveAction is used by users who "love" a specific entity.
type LoveAction 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 LoveActionSummary implements ActionSummary {
# The count of actions with this group.
count: Int
# The current user's action.
current_user: LoveAction
}
# A summary of counts related to all the Loves on an Asset.
type LoveAssetActionSummary implements AssetActionSummary {
# Number of loves associated with actionable types on this this Asset.
actionCount: Int
# Number of unique actionable types that are referenced by the loves.
actionableItemCount: Int
}
type CreateLoveResponse implements Response {
# The love that was created.
love: LoveAction
# An array of errors relating to the mutation that occurred.
errors: [UserError!]
}
type RootMutation {
# Creates a love on an entity.
createLove(love: CreateLoveInput!): CreateLoveResponse
}
@@ -0,0 +1,57 @@
import React from 'react';
import Icon from './Icon';
import styles from './styles.css';
import {withReaction} from 'plugin-api/beta/client/hocs';
import {t, can} from 'plugin-api/beta/client/services';
import cn from 'classnames';
const plugin = 'coral-plugin-respect';
class RespectButton extends React.Component {
handleClick = () => {
const {
postReaction,
deleteReaction,
showSignInDialog,
alreadyReacted,
user,
} = this.props;
// If the current user does not exist, trigger sign in dialog.
if (!user) {
showSignInDialog();
return;
}
// If the current user is suspended, do nothing.
if (!can(user, 'INTERACT_WITH_COMMUNITY')) {
return;
}
if (alreadyReacted) {
deleteReaction();
} else {
postReaction();
}
};
render() {
const {count, alreadyReacted} = this.props;
return (
<div className={cn(styles.container, `${plugin}-container`)}>
<button
className={cn(styles.button, {[styles.respected]: alreadyReacted}, `${plugin}-button`)}
onClick={this.handleClick}
>
<span className={`${plugin}-button-text`}>
{t(alreadyReacted ? 'coral-plugin-respect.respected' : 'coral-plugin-respect.respect')}
</span>
<Icon className={cn(styles.icon, `${plugin}-icon`)} />
<span className={cn(styles.icon, `${plugin}-count`)}>{count > 0 && count}</span>
</button>
</div>
);
}
}
export default withReaction('respect')(RespectButton);
@@ -1,6 +0,0 @@
import React from 'react';
import cn from 'classnames';
export default ({className}) => (
<i className={cn('fa', 'fa-handshake-o', className)} aria-hidden="true"/>
);
@@ -1,69 +0,0 @@
import React, {Component} from 'react';
import styles from './style.css';
import Icon from './Icon';
import t from 'coral-framework/services/i18n';
import cn from 'classnames';
import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils';
const name = 'coral-plugin-respect';
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={cn(styles.respect, `${name}-container`)}>
<button
className={cn(styles.button, {[styles.respected]: myRespect}, `${name}-button`)}
onClick={this.handleClick} >
<span className={`${name}-button-text`}>{t(myRespect ? 'respected' : 'respect')}</span>
<Icon className={cn(styles.icon, {[styles.respected]: myRespect}, `${name}-icon`)} />
<span className={`${name}-count`}>{count > 0 && count}</span>
</button>
</div>
);
}
}
RespectButton.propTypes = {
data: React.PropTypes.object.isRequired
};
export default RespectButton;
@@ -1,163 +0,0 @@
import {compose, gql} from 'react-apollo';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import get from 'lodash/get';
import {withFragments, withMutation} from 'coral-framework/hocs';
import {showSignInDialog} from 'coral-framework/actions/auth';
import RespectButton from '../components/RespectButton';
const isRespectAction = (a) => a.__typename === 'RespectActionSummary';
const COMMENT_FRAGMENT = gql`
fragment CoralRespect_UpdateFragment on Comment {
action_summaries {
... on RespectActionSummary {
count
current_user {
id
}
}
}
}
`;
const withDeleteAction = withMutation(gql`
mutation CoralRespect_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 = withMutation(gql`
mutation CoralRespect_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 CoralRespect_RespectButton_root on RootQuery {
me {
status
}
}
`,
comment: gql`
fragment CoralRespect_RespectButton_comment on Comment {
action_summaries {
... on RespectActionSummary {
count
current_user {
id
}
}
}
}`,
}),
connect(null, mapDispatchToProps),
withDeleteAction,
withPostRespect,
);
export default enhance(RespectButton);
+3 -3
View File
@@ -1,9 +1,9 @@
import RespectButton from './containers/RespectButton';
import translations from './translations.json';
import RespectButton from './RespectButton';
import translations from './translations.yml';
export default {
translations,
slots: {
commentActions: [RespectButton],
commentReactions: [RespectButton]
}
};
@@ -1,6 +1,6 @@
.respect {
.container {
display: inline-block;
}
}
.button {
color: #2a2a2a;
@@ -1,10 +0,0 @@
{
"en": {
"respect": "Respect",
"respected": "Respected"
},
"es": {
"respect": "Respetar",
"respected": "Respetado"
}
}
@@ -0,0 +1,9 @@
en:
coral-plugin-respect:
respect: Respect
respected: Respected
es:
coral-plugin-respect:
respect: Respetar
respected: Respetado
+2 -41
View File
@@ -1,41 +1,2 @@
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(_, {respect: {item_id, item_type}}, {mutators: {Action}}) {
return wrapResponse('respect')(
Action.create({item_id, item_type, action_type: 'RESPECT'})
);
}
}
},
hooks: {
Action: {
__resolveType: {
post({action_type}) {
switch (action_type) {
case 'RESPECT':
return 'RespectAction';
}
}
}
},
ActionSummary: {
__resolveType: {
post({action_type}) {
switch (action_type) {
case 'RESPECT':
return 'RespectActionSummary';
}
}
}
}
}
};
const {getReactionConfig} = require('../../plugin-api/beta/server');
module.exports = getReactionConfig('respect');
@@ -1,54 +0,0 @@
enum ACTION_TYPE {
# Represents a Respect.
RESPECT
}
input CreateRespectInput {
# The item's id for which we are to create a respect.
item_id: ID!
# The type of the item for which we are to create the respect.
item_type: ACTION_ITEM_TYPE!
}
# RespectAction is used by users who "respect" a specific entity.
type RespectAction 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 RespectActionSummary implements ActionSummary {
# The count of actions with this group.
count: Int
# The current user's action.
current_user: RespectAction
}
type CreateRespectResponse implements Response {
# The respect that was created.
respect: RespectAction
# An array of errors relating to the mutation that occurred.
errors: [UserError!]
}
type RootMutation {
# Creates a respect on an entity.
createRespect(respect: CreateRespectInput!): CreateRespectResponse
}
+41 -34
View File
@@ -30,9 +30,9 @@
version "0.8.6"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.8.6.tgz#b34fb880493ba835b0c067024ee70130d6f9bb68"
"@types/graphql@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.0.tgz#fccf859f0d2817687f210737dc3be48a18b1d754"
"@types/graphql@^0.9.0", "@types/graphql@^0.9.1":
version "0.9.1"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.1.tgz#b04ebe84bc997cc60dbea2ed4d0d4342c737f99d"
"@types/isomorphic-fetch@0.0.33":
version "0.0.33"
@@ -53,9 +53,9 @@
"@types/express-serve-static-core" "*"
"@types/mime" "*"
"@types/ws@0.0.38":
version "0.0.38"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-0.0.38.tgz#42106fff4b422ca956734e29f0d73a6d893194d3"
"@types/ws@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-3.0.0.tgz#4682a04d385484e73f7d8275cabc8b672d66143c"
dependencies:
"@types/node" "*"
@@ -2704,6 +2704,10 @@ es6-promise@^3.0.2, es6-promise@^3.2.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
es6-promise@^4.0.5:
version "4.1.0"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.0.tgz#dda03ca8f9f89bc597e689842929de7ba8cebdf0"
es6-set@~0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"
@@ -2941,7 +2945,7 @@ event-stream@~3.3.0:
stream-combiner "~0.0.4"
through "~2.3.1"
eventemitter3@^2.0.2:
eventemitter3@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
@@ -3631,13 +3635,15 @@ graphql-subscriptions@^0.2.0:
dependencies:
es6-promise "^3.2.1"
graphql-subscriptions@^0.3.0, graphql-subscriptions@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.3.1.tgz#0cedc2d507420cf26cf414080b079f05402f0303"
graphql-subscriptions@^0.4.3:
version "0.4.3"
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.4.3.tgz#2aed6ba87551cc747742b793497ff24b22991867"
dependencies:
es6-promise "^3.2.1"
"@types/graphql" "^0.9.1"
es6-promise "^4.0.5"
iterall "^1.1.1"
graphql-tag@^1.2.3, graphql-tag@^1.2.4:
graphql-tag@^1.2.3:
version "1.3.2"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-1.3.2.tgz#7abb3a8fd9f3415d07163314ed237061c785b759"
@@ -4456,6 +4462,10 @@ iterall@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.0.3.tgz#e0b31958f835013c323ff0b10943829ac69aa4b7"
iterall@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.1.tgz#f7f0af11e9a04ec6426260f5019d9fcca4d50214"
jju@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/jju/-/jju-1.3.0.tgz#dadd9ef01924bc728b03f2f7979bdbd62f7a2aaa"
@@ -5777,10 +5787,6 @@ optionator@^0.8.1, optionator@^0.8.2:
type-check "~0.3.2"
wordwrap "~1.0.0"
options@>=0.0.5:
version "0.0.6"
resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f"
os-browserify@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f"
@@ -7386,7 +7392,7 @@ rx@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
safe-buffer@^5.0.1:
safe-buffer@^5.0.1, safe-buffer@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
@@ -7834,19 +7840,20 @@ stylus@0.54.5, stylus@~0.54.5:
sax "0.5.x"
source-map "0.1.x"
subscriptions-transport-ws@^0.5.5-alpha.0:
version "0.5.5"
resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.5.5.tgz#7715ed4306f532a9dd53c02cbd789d2ee8871bb9"
subscriptions-transport-ws@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.7.2.tgz#26917fd4ba23fa37fcd28accb6fe36a040b3bbe9"
dependencies:
"@types/ws" "0.0.38"
"@types/ws" "^3.0.0"
backo2 "^1.0.2"
eventemitter3 "^2.0.2"
graphql "^0.9.1"
graphql-subscriptions "^0.3.0"
graphql-tag "^1.2.4"
eventemitter3 "^2.0.3"
graphql-subscriptions "^0.4.3"
graphql-tag "^2.0.0"
iterall "^1.1.1"
lodash.assign "^4.2.0"
lodash.isobject "^3.0.2"
lodash.isstring "^4.0.1"
ws "^1.1.0"
ws "^3.0.0"
sugarss@^0.2.0:
version "0.2.0"
@@ -8190,9 +8197,9 @@ uid-safe@2.1.4, uid-safe@~2.1.4:
dependencies:
random-bytes "~1.0.0"
ultron@1.0.x:
version "1.0.2"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
ultron@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864"
undefsafe@0.0.3:
version "0.0.3"
@@ -8510,12 +8517,12 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"
ws@^1.1.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.4.tgz#57f40d036832e5f5055662a397c4de76ed66bf61"
ws@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-3.0.0.tgz#98ddb00056c8390cb751e7788788497f99103b6c"
dependencies:
options ">=0.0.5"
ultron "1.0.x"
safe-buffer "~5.0.1"
ultron "~1.1.0"
x-xss-protection@1.0.0:
version "1.0.0"