diff --git a/.gitignore b/.gitignore
index 11b6af551..45de961a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,7 @@ plugins/*
!plugins/talk-plugin-mod
!plugins/talk-plugin-moderation-actions
!plugins/talk-plugin-notifications
+!plugins/talk-plugin-notifications-category-featured
!plugins/talk-plugin-notifications-category-reply
!plugins/talk-plugin-offtopic
!plugins/talk-plugin-permalink
diff --git a/docs/_docs/04-03-additional-plugins.md b/docs/_docs/04-03-additional-plugins.md
index 4f1654a4f..b88d8437c 100644
--- a/docs/_docs/04-03-additional-plugins.md
+++ b/docs/_docs/04-03-additional-plugins.md
@@ -149,3 +149,11 @@ Source: [plugins/talk-plugin-notifications-category-reply](https://github.com/co
Replies made to each user will trigger an email to be sent with the notification
details if enabled.
+
+### talk-plugin-notifications-category-featured
+{:.param}
+
+Source: [plugins/talk-plugin-notifications-category-featured](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-notifications-category-featured){:target="_blank"}
+
+When a comment is featured (via the `talk-plugin-featured-comments` plugin), the
+user will receive a notification email.
diff --git a/plugins/talk-plugin-notifications-category-featured/client/.eslintrc.json b/plugins/talk-plugin-notifications-category-featured/client/.eslintrc.json
new file mode 100644
index 000000000..c8a6db18a
--- /dev/null
+++ b/plugins/talk-plugin-notifications-category-featured/client/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "@coralproject/eslint-config-talk/client"
+}
diff --git a/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js
new file mode 100644
index 000000000..d7c6f6a0b
--- /dev/null
+++ b/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js
@@ -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 (
+
+ {t('talk-plugin-notifications-category-reply.toggle_description')}
+
+ );
+ }
+}
+
+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);
diff --git a/plugins/talk-plugin-notifications-category-featured/client/graphql.js b/plugins/talk-plugin-notifications-category-featured/client/graphql.js
new file mode 100644
index 000000000..440a6f7fa
--- /dev/null
+++ b/plugins/talk-plugin-notifications-category-featured/client/graphql.js
@@ -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 });
+ },
+ }),
+ },
+};
diff --git a/plugins/talk-plugin-notifications-category-featured/client/index.js b/plugins/talk-plugin-notifications-category-featured/client/index.js
new file mode 100644
index 000000000..1e22c3a93
--- /dev/null
+++ b/plugins/talk-plugin-notifications-category-featured/client/index.js
@@ -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,
+};
diff --git a/plugins/talk-plugin-notifications-category-featured/client/translations.yml b/plugins/talk-plugin-notifications-category-featured/client/translations.yml
new file mode 100644
index 000000000..7077943ef
--- /dev/null
+++ b/plugins/talk-plugin-notifications-category-featured/client/translations.yml
@@ -0,0 +1,3 @@
+en:
+ talk-plugin-notifications-category-featured:
+ toggle_description: My comment is featured
diff --git a/plugins/talk-plugin-notifications-category-featured/index.js b/plugins/talk-plugin-notifications-category-featured/index.js
new file mode 100644
index 000000000..802c37a9a
--- /dev/null
+++ b/plugins/talk-plugin-notifications-category-featured/index.js
@@ -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],
+};
diff --git a/plugins/talk-plugin-notifications-category-featured/translations.yml b/plugins/talk-plugin-notifications-category-featured/translations.yml
new file mode 100644
index 000000000..bd0d7412f
--- /dev/null
+++ b/plugins/talk-plugin-notifications-category-featured/translations.yml
@@ -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}"
\ No newline at end of file
diff --git a/plugins/talk-plugin-notifications-category-reply/.eslintrc.json b/plugins/talk-plugin-notifications-category-reply/.eslintrc.json
deleted file mode 100644
index 78f7c2397..000000000
--- a/plugins/talk-plugin-notifications-category-reply/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
-}
diff --git a/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js
index 61600a29f..2c9dd82e5 100644
--- a/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js
+++ b/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js
@@ -37,7 +37,7 @@ class ToggleContainer extends React.Component {
render() {
return (
- {t('talk-plugin-notifications-category-reply.toggle_description')}
+ {t('talk-plugin-notifications-category-featured.toggle_description')}
);
}
diff --git a/plugins/talk-plugin-notifications-category-reply/translations.yml b/plugins/talk-plugin-notifications-category-reply/translations.yml
index b3421a1a3..2599f8b59 100644
--- a/plugins/talk-plugin-notifications-category-reply/translations.yml
+++ b/plugins/talk-plugin-notifications-category-reply/translations.yml
@@ -2,5 +2,5 @@ en:
talk-plugin-notifications:
categories:
reply:
- subject: "Some has replied to your comment on [{0}]"
+ subject: "Someone has replied to your comment on [{0}]"
body: "{0}\n{1} replied to your comment: {2}"
\ No newline at end of file
diff --git a/plugins/talk-plugin-notifications/.eslintrc.json b/plugins/talk-plugin-notifications/.eslintrc.json
deleted file mode 100644
index 78f7c2397..000000000
--- a/plugins/talk-plugin-notifications/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@coralproject/eslint-config-talk"
-}
diff --git a/plugins/talk-plugin-notifications/client/containers/Settings.js b/plugins/talk-plugin-notifications/client/containers/Settings.js
index ff07c6433..e987bafa2 100644
--- a/plugins/talk-plugin-notifications/client/containers/Settings.js
+++ b/plugins/talk-plugin-notifications/client/containers/Settings.js
@@ -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);