Merge branch 'master' into staff-notifications

This commit is contained in:
Wyatt Johnson
2018-03-01 11:51:44 -07:00
committed by GitHub
17 changed files with 290 additions and 17 deletions
@@ -0,0 +1,3 @@
{
"extends": "@coralproject/eslint-config-talk/client"
}
@@ -0,0 +1,69 @@
import React from 'react';
import PropTypes from 'prop-types';
import { compose, gql } from 'react-apollo';
import Toggle from 'talk-plugin-notifications/client/components/Toggle';
import { t } from 'plugin-api/beta/client/services';
import { withFragments } from 'plugin-api/beta/client/hocs';
class ToggleContainer extends React.Component {
constructor(props) {
super(props);
props.setTurnOffInputFragment({ onFeatured: false });
if (this.getOnFeaturedSetting()) {
props.indicateOn();
}
}
componentWillReceiveProps(nextProps) {
const prevSetting = this.getOnFeaturedSetting(this.props);
const nextSetting = this.getOnFeaturedSetting(nextProps);
if (prevSetting && !nextSetting) {
nextProps.indicateOff();
} else if (!prevSetting && nextSetting) {
nextProps.indicateOn();
}
}
getOnFeaturedSetting = (props = this.props) =>
props.root.me.notificationSettings.onFeatured;
toggle = () => {
this.props.updateNotificationSettings({
onFeatured: !this.getOnFeaturedSetting(),
});
};
render() {
return (
<Toggle checked={this.getOnFeaturedSetting()} onChange={this.toggle}>
{t('talk-plugin-notifications-category-featured.toggle_description')}
</Toggle>
);
}
}
ToggleContainer.propTypes = {
data: PropTypes.object,
root: PropTypes.object,
indicateOn: PropTypes.func.isRequired,
indicateOff: PropTypes.func.isRequired,
setTurnOffInputFragment: PropTypes.func.isRequired,
updateNotificationSettings: PropTypes.func.isRequired,
};
const enhance = compose(
withFragments({
root: gql`
fragment TalkNotificationsCategoryFeatured_Toggle_root on RootQuery {
me {
notificationSettings {
onFeatured
}
}
}
`,
})
);
export default enhance(ToggleContainer);
@@ -0,0 +1,33 @@
import { gql } from 'react-apollo';
export default {
mutations: {
UpdateNotificationSettings: ({
variables: { input },
state: { auth: { user: { id } } },
}) => ({
update: proxy => {
if (input.onFeatured === undefined) {
return;
}
const fragment = gql`
fragment TalkNotificationsCategoryFeatured_User_Fragment on User {
notificationSettings {
onFeatured
}
}
`;
const fragmentId = `User_${id}`;
const data = {
__typename: 'User',
notificationSettings: {
__typename: 'NotificationSettings',
onFeatured: input.onFeatured,
},
};
proxy.writeFragment({ fragment, id: fragmentId, data });
},
}),
},
};
@@ -0,0 +1,11 @@
import Toggle from './containers/Toggle';
import translations from './translations.yml';
import graphql from './graphql';
export default {
slots: {
notificationSettings: [Toggle],
},
translations,
...graphql,
};
@@ -0,0 +1,3 @@
en:
talk-plugin-notifications-category-featured:
toggle_description: My comment is featured
@@ -0,0 +1,118 @@
const { graphql } = require('graphql');
const { get } = require('lodash');
const path = require('path');
const handle = async (ctx, { comment }) => {
const { connectors: { graph: { schema } } } = ctx;
// Check to see if this is a reply to an existing comment.
const commentID = get(comment, 'id', null);
if (commentID === null) {
ctx.log.debug('could not get comment id');
return;
}
// Execute the graph request.
const reply = await graphql(
schema,
`
query GetAuthorUserMetadata($comment_id: ID!) {
comment(id: $comment_id) {
id
user {
id
notificationSettings {
onFeatured
}
}
}
}
`,
{},
ctx,
{ comment_id: commentID }
);
if (reply.errors) {
ctx.log.error({ err: reply.errors }, 'could not query for author metadata');
return;
}
// Check if the user has notifications enabled.
const enabled = get(
reply,
'data.comment.user.notificationSettings.onFeatured',
false
);
if (!enabled) {
return;
}
const userID = get(reply, 'data.comment.user.id', null);
if (!userID) {
ctx.log.debug('could not get comment user id');
return;
}
// The user does have notifications for featured comments enabled, queue the
// notification to be sent.
return { userID, date: comment.created_at, context: comment.id };
};
const hydrate = async (ctx, category, context) => {
const { connectors: { graph: { schema } } } = ctx;
const reply = await graphql(
schema,
`
query GetNotificationData($context: ID!) {
comment(id: $context) {
id
asset {
title
url
}
}
}
`,
{},
ctx,
{ context }
);
if (reply.errors) {
throw reply.errors;
}
const comment = get(reply, 'data.comment');
const headline = get(comment, 'asset.title', null);
const assetURL = get(comment, 'asset.url', null);
const permalink = `${assetURL}?commentId=${comment.id}`;
return [headline, permalink];
};
const handler = {
handle,
category: 'featured',
event: 'commentFeatured',
hydrate,
};
module.exports = {
typeDefs: `
type NotificationSettings {
onFeatured: Boolean!
}
input NotificationSettingsInput {
onFeatured: Boolean
}
`,
resolvers: {
NotificationSettings: {
// onFeatured returns false by default if not specified.
onFeatured: settings => get(settings, 'onFeatured', false),
},
},
translations: path.join(__dirname, 'translations.yml'),
notifications: [handler],
};
@@ -0,0 +1,6 @@
en:
talk-plugin-notifications:
categories:
featured:
subject: "One of your comments was featured on [{0}]"
body: "{0}\nA member of our team has selected this comment to be featured for other readers: {1}"
@@ -1,3 +0,0 @@
{
"extends": "@coralproject/eslint-config-talk"
}
@@ -1,3 +0,0 @@
{
"extends": "@coralproject/eslint-config-talk"
}
@@ -23,9 +23,9 @@ class SettingsContainer extends React.Component {
hasNotifications: this.state.hasNotifications.filter(i => i !== plugin),
});
setTurnOffInputFragment = fragment =>
this.setState({
turnOffInput: { ...this.state.turnOffInput, ...fragment },
});
this.setState(state => ({
turnOffInput: { ...state.turnOffInput, ...fragment },
}));
turnOffAll = () => {
this.props.updateNotificationSettings(this.state.turnOffInput);