about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/components/actions/notifications.jsx14
-rw-r--r--app/assets/javascripts/components/features/notifications/components/column_settings.jsx150
-rw-r--r--app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx17
-rw-r--r--app/assets/javascripts/components/features/notifications/index.jsx14
-rw-r--r--app/assets/javascripts/components/features/ui/components/column.jsx3
-rw-r--r--app/assets/javascripts/components/locales/en.jsx8
-rw-r--r--app/assets/javascripts/components/reducers/notifications.jsx23
7 files changed, 221 insertions, 8 deletions
diff --git a/app/assets/javascripts/components/actions/notifications.jsx b/app/assets/javascripts/components/actions/notifications.jsx
index 6a8b1b05b..8bd835406 100644
--- a/app/assets/javascripts/components/actions/notifications.jsx
+++ b/app/assets/javascripts/components/actions/notifications.jsx
@@ -14,6 +14,8 @@ export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
 export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
 export const NOTIFICATIONS_EXPAND_FAIL    = 'NOTIFICATIONS_EXPAND_FAIL';
 
+export const NOTIFICATIONS_SETTING_CHANGE = 'NOTIFICATIONS_SETTING_CHANGE';
+
 const fetchRelatedRelationships = (dispatch, notifications) => {
   const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
 
@@ -23,7 +25,7 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
 };
 
 export function updateNotifications(notification, intlMessages, intlLocale) {
-  return dispatch => {
+  return (dispatch, getState) => {
     dispatch({
       type: NOTIFICATIONS_UPDATE,
       notification,
@@ -34,7 +36,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
     fetchRelatedRelationships(dispatch, [notification]);
 
     // Desktop notifications
-    if (typeof window.Notification !== 'undefined') {
+    if (typeof window.Notification !== 'undefined' && getState().getIn(['notifications', 'settings', 'alerts', notification.type], false)) {
       const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username });
       const body  = $('<p>').html(notification.status ? notification.status.content : '').text();
 
@@ -131,3 +133,11 @@ export function expandNotificationsFail(error) {
     error
   };
 };
+
+export function changeNotificationsSetting(key, checked) {
+  return {
+    type: NOTIFICATIONS_SETTING_CHANGE,
+    key,
+    checked
+  };
+};
diff --git a/app/assets/javascripts/components/features/notifications/components/column_settings.jsx b/app/assets/javascripts/components/features/notifications/components/column_settings.jsx
new file mode 100644
index 000000000..b4035c20d
--- /dev/null
+++ b/app/assets/javascripts/components/features/notifications/components/column_settings.jsx
@@ -0,0 +1,150 @@
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Toggle from 'react-toggle';
+import { Motion, spring } from 'react-motion';
+import { FormattedMessage } from 'react-intl';
+
+const outerStyle = {
+  background: '#373b4a',
+  padding: '15px'
+};
+
+const iconStyle = {
+  fontSize: '16px',
+  padding: '15px',
+  position: 'absolute',
+  right: '0',
+  top: '-48px',
+  cursor: 'pointer'
+};
+
+const labelStyle = {
+  display: 'block',
+  lineHeight: '24px',
+  verticalAlign: 'middle'
+};
+
+const labelSpanStyle = {
+  display: 'inline-block',
+  verticalAlign: 'middle',
+  marginBottom: '14px',
+  marginLeft: '8px',
+  color: '#9baec8'
+};
+
+const sectionStyle = {
+  cursor: 'default',
+  display: 'block',
+  fontWeight: '500',
+  color: '#9baec8',
+  marginBottom: '10px'
+};
+
+const rowStyle = {
+
+};
+
+const ColumnSettings = React.createClass({
+
+  propTypes: {
+    settings: ImmutablePropTypes.map.isRequired,
+    onChange: React.PropTypes.func.isRequired
+  },
+
+  getInitialState () {
+    return {
+      collapsed: true
+    };
+  },
+
+  mixins: [PureRenderMixin],
+
+  handleToggleCollapsed () {
+    this.setState({ collapsed: !this.state.collapsed });
+  },
+
+  handleChange (key, e) {
+    this.props.onChange(key, e.target.checked);
+  },
+
+  render () {
+    const { settings }  = this.props;
+    const { collapsed } = this.state;
+
+    const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
+    const showStr  = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
+
+    return (
+      <div style={{ position: 'relative' }}>
+        <div style={{...iconStyle, color: collapsed ? '#9baec8' : '#fff', background: collapsed ? '#2f3441' : '#373b4a' }} onClick={this.handleToggleCollapsed}><i className='fa fa-sliders' /></div>
+
+        <Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : 458) }}>
+          {({ opacity, height }) =>
+            <div style={{ overflow: 'hidden', height: `${height}px`, opacity: opacity / 100 }}>
+              <div style={outerStyle}>
+                <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
+
+                <div style={rowStyle}>
+                  <label style={labelStyle}>
+                    <Toggle checked={settings.getIn(['alerts', 'follow'])} onChange={this.handleChange.bind(this, ['alerts', 'follow'])} />
+                    <span style={labelSpanStyle}>{alertStr}</span>
+                  </label>
+
+                  <label style={labelStyle}>
+                    <Toggle checked={settings.getIn(['shows', 'follow'])} onChange={this.handleChange.bind(this, ['shows', 'follow'])} />
+                    <span style={labelSpanStyle}>{showStr}</span>
+                  </label>
+                </div>
+
+                <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
+
+                <div style={rowStyle}>
+                  <label style={labelStyle}>
+                    <Toggle checked={settings.getIn(['alerts', 'favourite'])} onChange={this.handleChange.bind(this, ['alerts', 'favourite'])} />
+                    <span style={labelSpanStyle}>{alertStr}</span>
+                  </label>
+
+                  <label style={labelStyle}>
+                    <Toggle checked={settings.getIn(['shows', 'favourite'])} onChange={this.handleChange.bind(this, ['shows', 'favourite'])} />
+                    <span style={labelSpanStyle}>{showStr}</span>
+                  </label>
+                </div>
+
+                <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
+
+                <div style={rowStyle}>
+                  <label style={labelStyle}>
+                    <Toggle checked={settings.getIn(['alerts', 'mention'])} onChange={this.handleChange.bind(this, ['alerts', 'mention'])} />
+                    <span style={labelSpanStyle}>{alertStr}</span>
+                  </label>
+
+                  <label style={labelStyle}>
+                    <Toggle checked={settings.getIn(['shows', 'mention'])} onChange={this.handleChange.bind(this, ['shows', 'mention'])} />
+                    <span style={labelSpanStyle}>{showStr}</span>
+                  </label>
+                </div>
+
+                <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
+
+                <div style={rowStyle}>
+                  <label style={labelStyle}>
+                    <Toggle checked={settings.getIn(['alerts', 'reblog'])} onChange={this.handleChange.bind(this, ['alerts', 'reblog'])} />
+                    <span style={labelSpanStyle}>{alertStr}</span>
+                  </label>
+
+                  <label style={labelStyle}>
+                    <Toggle checked={settings.getIn(['shows', 'reblog'])} onChange={this.handleChange.bind(this, ['shows', 'reblog'])} />
+                    <span style={labelSpanStyle}>{showStr}</span>
+                  </label>
+                </div>
+              </div>
+            </div>
+          }
+        </Motion>
+      </div>
+    );
+  }
+
+});
+
+export default ColumnSettings;
diff --git a/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx b/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx
new file mode 100644
index 000000000..6907fd351
--- /dev/null
+++ b/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx
@@ -0,0 +1,17 @@
+import { connect } from 'react-redux';
+import ColumnSettings from '../components/column_settings';
+import { changeNotificationsSetting } from '../../../actions/notifications';
+
+const mapStateToProps = state => ({
+  settings: state.getIn(['notifications', 'settings'])
+});
+
+const mapDispatchToProps = dispatch => ({
+
+  onChange (key, checked) {
+    dispatch(changeNotificationsSetting(key, checked));
+  }
+
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
diff --git a/app/assets/javascripts/components/features/notifications/index.jsx b/app/assets/javascripts/components/features/notifications/index.jsx
index 218196cfd..7e706ad6a 100644
--- a/app/assets/javascripts/components/features/notifications/index.jsx
+++ b/app/assets/javascripts/components/features/notifications/index.jsx
@@ -9,13 +9,21 @@ import {
 import NotificationContainer from './containers/notification_container';
 import { ScrollContainer } from 'react-router-scroll';
 import { defineMessages, injectIntl } from 'react-intl';
+import ColumnSettingsContainer from './containers/column_settings_container';
+import { createSelector } from 'reselect';
+import Immutable from 'immutable';
 
 const messages = defineMessages({
   title: { id: 'column.notifications', defaultMessage: 'Notifications' }
 });
 
+const getNotifications = createSelector([
+  state => Immutable.List(state.getIn(['notifications', 'settings', 'shows']).filter(item => !item).keys()),
+  state => state.getIn(['notifications', 'items'])
+], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type'))));
+
 const mapStateToProps = state => ({
-  notifications: state.getIn(['notifications', 'items'])
+  notifications: getNotifications(state)
 });
 
 const Notifications = React.createClass({
@@ -23,7 +31,8 @@ const Notifications = React.createClass({
   propTypes: {
     notifications: ImmutablePropTypes.list.isRequired,
     dispatch: React.PropTypes.func.isRequired,
-    trackScroll: React.PropTypes.bool
+    trackScroll: React.PropTypes.bool,
+    intl: React.PropTypes.object.isRequired
   },
 
   getDefaultProps () {
@@ -69,6 +78,7 @@ const Notifications = React.createClass({
     } else {
       return (
         <Column icon='bell' heading={intl.formatMessage(messages.title)}>
+          <ColumnSettingsContainer />
           {scrollableArea}
         </Column>
       );
diff --git a/app/assets/javascripts/components/features/ui/components/column.jsx b/app/assets/javascripts/components/features/ui/components/column.jsx
index c2060749a..c382e108d 100644
--- a/app/assets/javascripts/components/features/ui/components/column.jsx
+++ b/app/assets/javascripts/components/features/ui/components/column.jsx
@@ -40,7 +40,8 @@ const Column = React.createClass({
 
   propTypes: {
     heading: React.PropTypes.string,
-    icon: React.PropTypes.string
+    icon: React.PropTypes.string,
+    children: React.PropTypes.node
   },
 
   mixins: [PureRenderMixin],
diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx
index 50007a7da..3d4a38919 100644
--- a/app/assets/javascripts/components/locales/en.jsx
+++ b/app/assets/javascripts/components/locales/en.jsx
@@ -52,7 +52,13 @@ const en = {
   "notification.follow": "{name} followed you",
   "notification.favourite": "{name} favourited your status",
   "notification.reblog": "{name} boosted your status",
-  "notification.mention": "{name} mentioned you"
+  "notification.mention": "{name} mentioned you",
+  "notifications.column_settings.alert": "Desktop notifications",
+  "notifications.column_settings.show": "Show in column",
+  "notifications.column_settings.follow": "New followers:",
+  "notifications.column_settings.favourite": "Favourites:",
+  "notifications.column_settings.mention": "Mentions:",
+  "notifications.column_settings.reblog": "Boosts:",
 };
 
 export default en;
diff --git a/app/assets/javascripts/components/reducers/notifications.jsx b/app/assets/javascripts/components/reducers/notifications.jsx
index 617a833d2..e0d1ccf83 100644
--- a/app/assets/javascripts/components/reducers/notifications.jsx
+++ b/app/assets/javascripts/components/reducers/notifications.jsx
@@ -1,7 +1,8 @@
 import {
   NOTIFICATIONS_UPDATE,
   NOTIFICATIONS_REFRESH_SUCCESS,
-  NOTIFICATIONS_EXPAND_SUCCESS
+  NOTIFICATIONS_EXPAND_SUCCESS,
+  NOTIFICATIONS_SETTING_CHANGE
 } from '../actions/notifications';
 import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts';
 import Immutable from 'immutable';
@@ -9,7 +10,23 @@ import Immutable from 'immutable';
 const initialState = Immutable.Map({
   items: Immutable.List(),
   next: null,
-  loaded: false
+  loaded: false,
+
+  settings: Immutable.Map({
+    alerts: Immutable.Map({
+      follow: true,
+      favourite: true,
+      reblog: true,
+      mention: true
+    }),
+
+    shows: Immutable.Map({
+      follow: true,
+      favourite: true,
+      reblog: true,
+      mention: true
+    })
+  })
 });
 
 const notificationToMap = notification => Immutable.Map({
@@ -58,6 +75,8 @@ export default function notifications(state = initialState, action) {
       return appendNormalizedNotifications(state, action.notifications, action.next);
     case ACCOUNT_BLOCK_SUCCESS:
       return filterNotifications(state, action.relationship);
+    case NOTIFICATIONS_SETTING_CHANGE:
+      return state.setIn(['settings', ...action.key], action.checked);
     default:
       return state;
   }