about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features/notifications/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/features/notifications/components')
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/clear_column_button.js17
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/column_settings.js85
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/follow.js98
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/notification.js104
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/overlay.js57
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js34
6 files changed, 395 insertions, 0 deletions
diff --git a/app/javascript/flavours/glitch/features/notifications/components/clear_column_button.js b/app/javascript/flavours/glitch/features/notifications/components/clear_column_button.js
new file mode 100644
index 000000000..22a10753f
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/notifications/components/clear_column_button.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+
+export default class ClearColumnButton extends React.Component {
+
+  static propTypes = {
+    onClick: PropTypes.func.isRequired,
+  };
+
+  render () {
+    return (
+      <button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.props.onClick}><i className='fa fa-eraser' /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
new file mode 100644
index 000000000..d9638aaf3
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
@@ -0,0 +1,85 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { FormattedMessage } from 'react-intl';
+import ClearColumnButton from './clear_column_button';
+import SettingToggle from './setting_toggle';
+
+export default class ColumnSettings extends React.PureComponent {
+
+  static propTypes = {
+    settings: ImmutablePropTypes.map.isRequired,
+    pushSettings: ImmutablePropTypes.map.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onClear: PropTypes.func.isRequired,
+  };
+
+  onPushChange = (path, checked) => {
+    this.props.onChange(['push', ...path], checked);
+  }
+
+  render () {
+    const { settings, pushSettings, onChange, onClear } = this.props;
+
+    const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
+    const showStr  = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
+    const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
+
+    const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
+    const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
+    const pushMeta = showPushSettings && <FormattedMessage id='notifications.column_settings.push_meta' defaultMessage='This device' />;
+
+    return (
+      <div>
+        <div className='column-settings__row'>
+          <ClearColumnButton onClick={onClear} />
+        </div>
+
+        <div role='group' aria-labelledby='notifications-follow'>
+          <span id='notifications-follow' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
+
+          <div className='column-settings__row'>
+            <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} />
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
+          </div>
+        </div>
+
+        <div role='group' aria-labelledby='notifications-favourite'>
+          <span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
+
+          <div className='column-settings__row'>
+            <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'favourite']} onChange={onChange} label={showStr} />
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
+          </div>
+        </div>
+
+        <div role='group' aria-labelledby='notifications-mention'>
+          <span id='notifications-mention' className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
+
+          <div className='column-settings__row'>
+            <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} />
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'mention']} onChange={onChange} label={showStr} />
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'mention']} onChange={onChange} label={soundStr} />
+          </div>
+        </div>
+
+        <div role='group' aria-labelledby='notifications-reblog'>
+          <span id='notifications-reblog' className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
+
+          <div className='column-settings__row'>
+            <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
+            {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'reblog']} onChange={onChange} label={showStr} />
+            <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow.js b/app/javascript/flavours/glitch/features/notifications/components/follow.js
new file mode 100644
index 000000000..54506f67c
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/notifications/components/follow.js
@@ -0,0 +1,98 @@
+//  Package imports.
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { HotKeys } from 'react-hotkeys';
+
+// Our imports.
+import Permalink from 'flavours/glitch/components/permalink';
+import AccountContainer from 'flavours/glitch/containers/account_container';
+import NotificationOverlayContainer from '../containers/overlay_container';
+
+export default class NotificationFollow extends ImmutablePureComponent {
+
+  static propTypes = {
+    hidden: PropTypes.bool,
+    id: PropTypes.string.isRequired,
+    account: ImmutablePropTypes.map.isRequired,
+    notification: ImmutablePropTypes.map.isRequired,
+  };
+
+  handleMoveUp = () => {
+    const { notification, onMoveUp } = this.props;
+    onMoveUp(notification.get('id'));
+  }
+
+  handleMoveDown = () => {
+    const { notification, onMoveDown } = this.props;
+    onMoveDown(notification.get('id'));
+  }
+
+  handleOpen = () => {
+    this.handleOpenProfile();
+  }
+
+  handleOpenProfile = () => {
+    const { notification } = this.props;
+    this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`);
+  }
+
+  handleMention = e => {
+    e.preventDefault();
+
+    const { notification, onMention } = this.props;
+    onMention(notification.get('account'), this.context.router.history);
+  }
+
+  getHandlers () {
+    return {
+      moveUp: this.handleMoveUp,
+      moveDown: this.handleMoveDown,
+      open: this.handleOpen,
+      openProfile: this.handleOpenProfile,
+      mention: this.handleMention,
+      reply: this.handleMention,
+    };
+  }
+
+  render () {
+    const { account, notification, hidden } = this.props;
+
+    //  Links to the display name.
+    const displayName = account.get('display_name_html') || account.get('username');
+    const link = (
+      <Permalink
+        className='notification__display-name'
+        href={account.get('url')}
+        title={account.get('acct')}
+        to={`/accounts/${account.get('id')}`}
+        dangerouslySetInnerHTML={{ __html: displayName }}
+      />
+    );
+
+    //  Renders.
+    return (
+      <HotKeys handlers={this.getHandlers()}>
+        <div className='notification notification-follow focusable' tabIndex='0'>
+          <div className='notification__message'>
+            <div className='notification__favourite-icon-wrapper'>
+              <i className='fa fa-fw fa-user-plus' />
+            </div>
+
+            <FormattedMessage
+              id='notification.follow'
+              defaultMessage='{name} followed you'
+              values={{ name: link }}
+            />
+          </div>
+
+          <AccountContainer hidden={hidden} id={account.get('id')} withNote={false} />
+          <NotificationOverlayContainer notification={notification} />
+        </div>
+      </HotKeys>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.js b/app/javascript/flavours/glitch/features/notifications/components/notification.js
new file mode 100644
index 000000000..21c55accc
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/notifications/components/notification.js
@@ -0,0 +1,104 @@
+//  Package imports.
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+//  Our imports,
+import StatusContainer from 'flavours/glitch/containers/status_container';
+import NotificationFollow from './follow';
+
+export default class Notification extends ImmutablePureComponent {
+
+  static propTypes = {
+    notification: ImmutablePropTypes.map.isRequired,
+    hidden: PropTypes.bool,
+    onMoveUp: PropTypes.func.isRequired,
+    onMoveDown: PropTypes.func.isRequired,
+    onMention: PropTypes.func.isRequired,
+    getScrollPosition: PropTypes.func,
+    updateScrollBottom: PropTypes.func,
+  };
+
+  render () {
+    const {
+      hidden,
+      notification,
+      onMoveDown,
+      onMoveUp,
+      onMention,
+      getScrollPosition,
+      updateScrollBottom,
+    } = this.props;
+
+    switch(notification.get('type')) {
+    case 'follow':
+      return (
+        <NotificationFollow
+          hidden={hidden}
+          id={notification.get('id')}
+          account={notification.get('account')}
+          notification={notification}
+          onMoveDown={onMoveDown}
+          onMoveUp={onMoveUp}
+          onMention={onMention}
+        />
+      );
+    case 'mention':
+      return (
+        <StatusContainer
+          containerId={notification.get('id')}
+          hidden={hidden}
+          id={notification.get('status')}
+          notification={notification}
+          onMoveDown={onMoveDown}
+          onMoveUp={onMoveUp}
+          onMention={onMention}
+          contextType='notifications'
+          getScrollPosition={getScrollPosition}
+          updateScrollBottom={updateScrollBottom}
+          withDismiss
+        />
+      );
+    case 'favourite':
+      return (
+        <StatusContainer
+          containerId={notification.get('id')}
+          hidden={hidden}
+          id={notification.get('status')}
+          account={notification.get('account')}
+          prepend='favourite'
+          muted
+          notification={notification}
+          onMoveDown={onMoveDown}
+          onMoveUp={onMoveUp}
+          onMention={onMention}
+          getScrollPosition={getScrollPosition}
+          updateScrollBottom={updateScrollBottom}
+          withDismiss
+        />
+      );
+    case 'reblog':
+      return (
+        <StatusContainer
+          containerId={notification.get('id')}
+          hidden={hidden}
+          id={notification.get('status')}
+          account={notification.get('account')}
+          prepend='reblog'
+          muted
+          notification={notification}
+          onMoveDown={onMoveDown}
+          onMoveUp={onMoveUp}
+          onMention={onMention}
+          getScrollPosition={getScrollPosition}
+          updateScrollBottom={updateScrollBottom}
+          withDismiss
+        />
+      );
+    default:
+      return null;
+    }
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/notifications/components/overlay.js b/app/javascript/flavours/glitch/features/notifications/components/overlay.js
new file mode 100644
index 000000000..e56f9c628
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/notifications/components/overlay.js
@@ -0,0 +1,57 @@
+/**
+ * Notification overlay
+ */
+
+
+//  Package imports.
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  markForDeletion: { id: 'notification.markForDeletion', defaultMessage: 'Mark for deletion' },
+});
+
+@injectIntl
+export default class NotificationOverlay extends ImmutablePureComponent {
+
+  static propTypes = {
+    notification    : ImmutablePropTypes.map.isRequired,
+    onMarkForDelete : PropTypes.func.isRequired,
+    show            : PropTypes.bool.isRequired,
+    intl            : PropTypes.object.isRequired,
+  };
+
+  onToggleMark = () => {
+    const mark = !this.props.notification.get('markedForDelete');
+    const id = this.props.notification.get('id');
+    this.props.onMarkForDelete(id, mark);
+  }
+
+  render () {
+    const { notification, show, intl } = this.props;
+
+    const active = notification.get('markedForDelete');
+    const label = intl.formatMessage(messages.markForDeletion);
+
+    return show ? (
+      <div
+        aria-label={label}
+        role='checkbox'
+        aria-checked={active}
+        tabIndex={0}
+        className={`notification__dismiss-overlay ${active ? 'active' : ''}`}
+        onClick={this.onToggleMark}
+      >
+        <div className='wrappy'>
+          <div className='ckbox' aria-hidden='true' title={label}>
+            {active ? (<i className='fa fa-check' />) : ''}
+          </div>
+        </div>
+      </div>
+    ) : null;
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js b/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js
new file mode 100644
index 000000000..ac2211e48
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Toggle from 'react-toggle';
+
+export default class SettingToggle extends React.PureComponent {
+
+  static propTypes = {
+    prefix: PropTypes.string,
+    settings: ImmutablePropTypes.map.isRequired,
+    settingPath: PropTypes.array.isRequired,
+    label: PropTypes.node.isRequired,
+    meta: PropTypes.node,
+    onChange: PropTypes.func.isRequired,
+  }
+
+  onChange = ({ target }) => {
+    this.props.onChange(this.props.settingPath, target.checked);
+  }
+
+  render () {
+    const { prefix, settings, settingPath, label, meta } = this.props;
+    const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-');
+
+    return (
+      <div className='setting-toggle'>
+        <Toggle id={id} checked={settings.getIn(settingPath)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
+        <label htmlFor={id} className='setting-toggle__label'>{label}</label>
+        {meta && <span className='setting-meta__label'>{meta}</span>}
+      </div>
+    );
+  }
+
+}