[Glitch] Fix browser notification permission request logic
Port 592fa427e3 to glitch-soc
Signed-off-by: Thibaut Girka <thib@sitedethib.com>
			
			
This commit is contained in:
		
							parent
							
								
									d890bf9478
								
							
						
					
					
						commit
						8fb1cbf6fe
					
				
					 14 changed files with 202 additions and 18 deletions
				
			
		| 
						 | 
					@ -16,6 +16,7 @@ import { getFiltersRegex } from 'flavours/glitch/selectors';
 | 
				
			||||||
import { usePendingItems as preferPendingItems } from 'flavours/glitch/util/initial_state';
 | 
					import { usePendingItems as preferPendingItems } from 'flavours/glitch/util/initial_state';
 | 
				
			||||||
import compareId from 'flavours/glitch/util/compare_id';
 | 
					import compareId from 'flavours/glitch/util/compare_id';
 | 
				
			||||||
import { searchTextFromRawStatus } from 'flavours/glitch/actions/importer/normalizer';
 | 
					import { searchTextFromRawStatus } from 'flavours/glitch/actions/importer/normalizer';
 | 
				
			||||||
 | 
					import { requestNotificationPermission } from 'flavours/glitch/util/notifications';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
 | 
					export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
 | 
				
			||||||
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
 | 
					export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
 | 
				
			||||||
| 
						 | 
					@ -46,8 +47,12 @@ export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const NOTIFICATIONS_SET_VISIBILITY = 'NOTIFICATIONS_SET_VISIBILITY';
 | 
					export const NOTIFICATIONS_SET_VISIBILITY = 'NOTIFICATIONS_SET_VISIBILITY';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
 | 
					export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const NOTIFICATIONS_SET_BROWSER_SUPPORT    = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
 | 
				
			||||||
 | 
					export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineMessages({
 | 
					defineMessages({
 | 
				
			||||||
  mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
 | 
					  mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -327,3 +332,42 @@ export function markNotificationsAsRead() {
 | 
				
			||||||
    type: NOTIFICATIONS_MARK_AS_READ,
 | 
					    type: NOTIFICATIONS_MARK_AS_READ,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Browser support
 | 
				
			||||||
 | 
					export function setupBrowserNotifications() {
 | 
				
			||||||
 | 
					  return dispatch => {
 | 
				
			||||||
 | 
					    dispatch(setBrowserSupport('Notification' in window));
 | 
				
			||||||
 | 
					    if ('Notification' in window) {
 | 
				
			||||||
 | 
					      dispatch(setBrowserPermission(Notification.permission));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ('Notification' in window && 'permissions' in navigator) {
 | 
				
			||||||
 | 
					      navigator.permissions.query({ name: 'notifications' }).then((status) => {
 | 
				
			||||||
 | 
					        status.onchange = () => dispatch(setBrowserPermission(Notification.permission));
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function requestBrowserPermission(callback = noOp) {
 | 
				
			||||||
 | 
					  return dispatch => {
 | 
				
			||||||
 | 
					    requestNotificationPermission((permission) => {
 | 
				
			||||||
 | 
					      dispatch(setBrowserPermission(permission));
 | 
				
			||||||
 | 
					      callback(permission);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function setBrowserSupport (value) {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: NOTIFICATIONS_SET_BROWSER_SUPPORT,
 | 
				
			||||||
 | 
					    value,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function setBrowserPermission (value) {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: NOTIFICATIONS_SET_BROWSER_PERMISSION,
 | 
				
			||||||
 | 
					    value,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,6 +34,7 @@ class ColumnHeader extends React.PureComponent {
 | 
				
			||||||
    onMove: PropTypes.func,
 | 
					    onMove: PropTypes.func,
 | 
				
			||||||
    onClick: PropTypes.func,
 | 
					    onClick: PropTypes.func,
 | 
				
			||||||
    appendContent: PropTypes.node,
 | 
					    appendContent: PropTypes.node,
 | 
				
			||||||
 | 
					    collapseIssues: PropTypes.bool,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  state = {
 | 
					  state = {
 | 
				
			||||||
| 
						 | 
					@ -88,7 +89,7 @@ class ColumnHeader extends React.PureComponent {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent } = this.props;
 | 
					    const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
 | 
				
			||||||
    const { collapsed, animating } = this.state;
 | 
					    const { collapsed, animating } = this.state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const wrapperClassName = classNames('column-header__wrapper', {
 | 
					    const wrapperClassName = classNames('column-header__wrapper', {
 | 
				
			||||||
| 
						 | 
					@ -150,7 +151,20 @@ class ColumnHeader extends React.PureComponent {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (children || (multiColumn && this.props.onPin)) {
 | 
					    if (children || (multiColumn && this.props.onPin)) {
 | 
				
			||||||
      collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><Icon id='sliders' /></button>;
 | 
					      collapseButton = (
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          className={collapsibleButtonClassName}
 | 
				
			||||||
 | 
					          title={formatMessage(collapsed ? messages.show : messages.hide)}
 | 
				
			||||||
 | 
					          aria-label={formatMessage(collapsed ? messages.show : messages.hide)}
 | 
				
			||||||
 | 
					          aria-pressed={collapsed ? 'false' : 'true'}
 | 
				
			||||||
 | 
					          onClick={this.handleToggleClick}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <i className='icon-with-badge'>
 | 
				
			||||||
 | 
					            <Icon id='sliders' />
 | 
				
			||||||
 | 
					            {collapseIssues && <i className='icon-with-badge__issue-badge' />}
 | 
				
			||||||
 | 
					          </i>
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const hasTitle = icon && title;
 | 
					    const hasTitle = icon && title;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,16 +4,18 @@ import Icon from 'flavours/glitch/components/icon';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const formatNumber = num => num > 40 ? '40+' : num;
 | 
					const formatNumber = num => num > 40 ? '40+' : num;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const IconWithBadge = ({ id, count, className }) => (
 | 
					const IconWithBadge = ({ id, count, issueBadge, className }) => (
 | 
				
			||||||
  <i className='icon-with-badge'>
 | 
					  <i className='icon-with-badge'>
 | 
				
			||||||
    <Icon id={id} fixedWidth className={className} />
 | 
					    <Icon id={id} fixedWidth className={className} />
 | 
				
			||||||
    {count > 0 && <i className='icon-with-badge__badge'>{formatNumber(count)}</i>}
 | 
					    {count > 0 && <i className='icon-with-badge__badge'>{formatNumber(count)}</i>}
 | 
				
			||||||
 | 
					    {issueBadge && <i className='icon-with-badge__issue-badge' />}
 | 
				
			||||||
  </i>
 | 
					  </i>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
IconWithBadge.propTypes = {
 | 
					IconWithBadge.propTypes = {
 | 
				
			||||||
  id: PropTypes.string.isRequired,
 | 
					  id: PropTypes.string.isRequired,
 | 
				
			||||||
  count: PropTypes.number.isRequired,
 | 
					  count: PropTypes.number.isRequired,
 | 
				
			||||||
 | 
					  issueBadge: PropTypes.bool,
 | 
				
			||||||
  className: PropTypes.string,
 | 
					  className: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,13 +32,6 @@ export default class Mastodon extends React.PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentDidMount() {
 | 
					  componentDidMount() {
 | 
				
			||||||
    this.disconnect = store.dispatch(connectUserStream());
 | 
					    this.disconnect = store.dispatch(connectUserStream());
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Desktop notifications
 | 
					 | 
				
			||||||
    // Ask after 1 minute
 | 
					 | 
				
			||||||
    if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
 | 
					 | 
				
			||||||
      window.setTimeout(() => Notification.requestPermission(), 60 * 1000);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    store.dispatch(showOnboardingOnce());
 | 
					    store.dispatch(showOnboardingOnce());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
				
			||||||
import { FormattedMessage } from 'react-intl';
 | 
					import { FormattedMessage } from 'react-intl';
 | 
				
			||||||
import ClearColumnButton from './clear_column_button';
 | 
					import ClearColumnButton from './clear_column_button';
 | 
				
			||||||
import SettingToggle from './setting_toggle';
 | 
					import SettingToggle from './setting_toggle';
 | 
				
			||||||
 | 
					import Icon from 'flavours/glitch/components/icon';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class ColumnSettings extends React.PureComponent {
 | 
					export default class ColumnSettings extends React.PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +13,10 @@ export default class ColumnSettings extends React.PureComponent {
 | 
				
			||||||
    pushSettings: ImmutablePropTypes.map.isRequired,
 | 
					    pushSettings: ImmutablePropTypes.map.isRequired,
 | 
				
			||||||
    onChange: PropTypes.func.isRequired,
 | 
					    onChange: PropTypes.func.isRequired,
 | 
				
			||||||
    onClear: PropTypes.func.isRequired,
 | 
					    onClear: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					    onRequestNotificationPermission: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					    alertsEnabled: PropTypes.bool,
 | 
				
			||||||
 | 
					    browserSupport: PropTypes.bool,
 | 
				
			||||||
 | 
					    browserPermission: PropTypes.bool,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onPushChange = (path, checked) => {
 | 
					  onPushChange = (path, checked) => {
 | 
				
			||||||
| 
						 | 
					@ -19,7 +24,7 @@ export default class ColumnSettings extends React.PureComponent {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  render () {
 | 
				
			||||||
    const { settings, pushSettings, onChange, onClear } = this.props;
 | 
					    const { settings, pushSettings, onChange, onClear, onRequestNotificationPermission, alertsEnabled, browserSupport, browserPermission } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const filterShowStr = <FormattedMessage id='notifications.column_settings.filter_bar.show' defaultMessage='Show' />;
 | 
					    const filterShowStr = <FormattedMessage id='notifications.column_settings.filter_bar.show' defaultMessage='Show' />;
 | 
				
			||||||
    const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />;
 | 
					    const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />;
 | 
				
			||||||
| 
						 | 
					@ -31,8 +36,40 @@ export default class ColumnSettings extends React.PureComponent {
 | 
				
			||||||
    const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
 | 
					    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' />;
 | 
					    const pushMeta = showPushSettings && <FormattedMessage id='notifications.column_settings.push_meta' defaultMessage='This device' />;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const settingsIssues = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (alertsEnabled && browserSupport && browserPermission !== 'granted') {
 | 
				
			||||||
 | 
					      if (browserPermission === 'denied') {
 | 
				
			||||||
 | 
					        settingsIssues.push(
 | 
				
			||||||
 | 
					          <button
 | 
				
			||||||
 | 
					            className='text-btn column-header__issue-btn'
 | 
				
			||||||
 | 
					            tabIndex='0'
 | 
				
			||||||
 | 
					            onClick={onRequestNotificationPermission}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <Icon id='exclamation-circle' /> <FormattedMessage id='notifications.permission_denied' defaultMessage='Mastodon cannot show notifications because the permission has been denied' />
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      } else if (browserPermission === 'default') {
 | 
				
			||||||
 | 
					        settingsIssues.push(
 | 
				
			||||||
 | 
					          <button
 | 
				
			||||||
 | 
					            className='text-btn column-header__issue-btn'
 | 
				
			||||||
 | 
					            tabIndex='0'
 | 
				
			||||||
 | 
					            onClick={onRequestNotificationPermission}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <Icon id='exclamation-circle' /> <FormattedMessage id='notifications.request_permission' defaultMessage='Enable browser notifications' />
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
 | 
					        {settingsIssues && (
 | 
				
			||||||
 | 
					          <div className='column-settings__row'>
 | 
				
			||||||
 | 
					            {settingsIssues}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div className='column-settings__row'>
 | 
					        <div className='column-settings__row'>
 | 
				
			||||||
          <ClearColumnButton onClick={onClear} />
 | 
					          <ClearColumnButton onClick={onClear} />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,28 +3,55 @@ import { defineMessages, injectIntl } from 'react-intl';
 | 
				
			||||||
import ColumnSettings from '../components/column_settings';
 | 
					import ColumnSettings from '../components/column_settings';
 | 
				
			||||||
import { changeSetting } from 'flavours/glitch/actions/settings';
 | 
					import { changeSetting } from 'flavours/glitch/actions/settings';
 | 
				
			||||||
import { setFilter } from 'flavours/glitch/actions/notifications';
 | 
					import { setFilter } from 'flavours/glitch/actions/notifications';
 | 
				
			||||||
import { clearNotifications } from 'flavours/glitch/actions/notifications';
 | 
					import { clearNotifications, requestBrowserPermission } from 'flavours/glitch/actions/notifications';
 | 
				
			||||||
import { changeAlerts as changePushNotifications } from 'flavours/glitch/actions/push_notifications';
 | 
					import { changeAlerts as changePushNotifications } from 'flavours/glitch/actions/push_notifications';
 | 
				
			||||||
import { openModal } from 'flavours/glitch/actions/modal';
 | 
					import { openModal } from 'flavours/glitch/actions/modal';
 | 
				
			||||||
 | 
					import { showAlert } from 'flavours/glitch/actions/alerts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
  clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' },
 | 
					  clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' },
 | 
				
			||||||
  clearConfirm: { id: 'notifications.clear', defaultMessage: 'Clear notifications' },
 | 
					  clearConfirm: { id: 'notifications.clear', defaultMessage: 'Clear notifications' },
 | 
				
			||||||
 | 
					  permissionDenied: { id: 'notifications.permission_denied', defaultMessage: 'Cannot enable desktop notifications as permission has been denied.' },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mapStateToProps = state => ({
 | 
					const mapStateToProps = state => ({
 | 
				
			||||||
  settings: state.getIn(['settings', 'notifications']),
 | 
					  settings: state.getIn(['settings', 'notifications']),
 | 
				
			||||||
  pushSettings: state.get('push_notifications'),
 | 
					  pushSettings: state.get('push_notifications'),
 | 
				
			||||||
 | 
					  alertsEnabled: state.getIn(['settings', 'notifications', 'alerts']).includes(true),
 | 
				
			||||||
 | 
					  browserSupport: state.getIn(['notifications', 'browserSupport']),
 | 
				
			||||||
 | 
					  browserPermission: state.getIn(['notifications', 'browserPermission']),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mapDispatchToProps = (dispatch, { intl }) => ({
 | 
					const mapDispatchToProps = (dispatch, { intl }) => ({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onChange (path, checked) {
 | 
					  onChange (path, checked) {
 | 
				
			||||||
    if (path[0] === 'push') {
 | 
					    if (path[0] === 'push') {
 | 
				
			||||||
 | 
					      if (checked && typeof window.Notification !== 'undefined' && Notification.permission !== 'granted') {
 | 
				
			||||||
 | 
					        dispatch(requestBrowserPermission((permission) => {
 | 
				
			||||||
 | 
					          if (permission === 'granted') {
 | 
				
			||||||
            dispatch(changePushNotifications(path.slice(1), checked));
 | 
					            dispatch(changePushNotifications(path.slice(1), checked));
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            dispatch(showAlert(undefined, messages.permissionDenied));
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        dispatch(changePushNotifications(path.slice(1), checked));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } else if (path[0] === 'quickFilter') {
 | 
					    } else if (path[0] === 'quickFilter') {
 | 
				
			||||||
      dispatch(changeSetting(['notifications', ...path], checked));
 | 
					      dispatch(changeSetting(['notifications', ...path], checked));
 | 
				
			||||||
      dispatch(setFilter('all'));
 | 
					      dispatch(setFilter('all'));
 | 
				
			||||||
 | 
					    } else if (path[0] === 'alerts' && checked && typeof window.Notification !== 'undefined' && Notification.permission !== 'granted') {
 | 
				
			||||||
 | 
					      if (checked && typeof window.Notification !== 'undefined' && Notification.permission !== 'granted') {
 | 
				
			||||||
 | 
					        dispatch(requestBrowserPermission((permission) => {
 | 
				
			||||||
 | 
					          if (permission === 'granted') {
 | 
				
			||||||
 | 
					            dispatch(changeSetting(['notifications', ...path], checked));
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            dispatch(showAlert(undefined, messages.permissionDenied));
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        dispatch(changeSetting(['notifications', ...path], checked));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      dispatch(changeSetting(['notifications', ...path], checked));
 | 
					      dispatch(changeSetting(['notifications', ...path], checked));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -38,6 +65,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 | 
				
			||||||
    }));
 | 
					    }));
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onRequestNotificationPermission () {
 | 
				
			||||||
 | 
					    dispatch(requestBrowserPermission());
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ColumnSettings));
 | 
					export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ColumnSettings));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -62,6 +62,7 @@ const mapStateToProps = state => ({
 | 
				
			||||||
  notifCleaningActive: state.getIn(['notifications', 'cleaningMode']),
 | 
					  notifCleaningActive: state.getIn(['notifications', 'cleaningMode']),
 | 
				
			||||||
  lastReadId: state.getIn(['notifications', 'readMarkerId']),
 | 
					  lastReadId: state.getIn(['notifications', 'readMarkerId']),
 | 
				
			||||||
  canMarkAsRead: state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
 | 
					  canMarkAsRead: state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
 | 
				
			||||||
 | 
					  needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) !== 'granted',
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* glitch */
 | 
					/* glitch */
 | 
				
			||||||
| 
						 | 
					@ -105,6 +106,7 @@ class Notifications extends React.PureComponent {
 | 
				
			||||||
    onUnmount: PropTypes.func,
 | 
					    onUnmount: PropTypes.func,
 | 
				
			||||||
    lastReadId: PropTypes.string,
 | 
					    lastReadId: PropTypes.string,
 | 
				
			||||||
    canMarkAsRead: PropTypes.bool,
 | 
					    canMarkAsRead: PropTypes.bool,
 | 
				
			||||||
 | 
					    needsNotificationPermission: PropTypes.bool,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static defaultProps = {
 | 
					  static defaultProps = {
 | 
				
			||||||
| 
						 | 
					@ -333,6 +335,7 @@ class Notifications extends React.PureComponent {
 | 
				
			||||||
          multiColumn={multiColumn}
 | 
					          multiColumn={multiColumn}
 | 
				
			||||||
          localSettings={this.props.localSettings}
 | 
					          localSettings={this.props.localSettings}
 | 
				
			||||||
          extraButton={extraButtons}
 | 
					          extraButton={extraButtons}
 | 
				
			||||||
 | 
					          collapseIssues={this.props.needsNotificationPermission}
 | 
				
			||||||
          appendContent={notifCleaningDrawer}
 | 
					          appendContent={notifCleaningDrawer}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <ColumnSettingsContainer />
 | 
					          <ColumnSettingsContainer />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ import IconWithBadge from 'flavours/glitch/components/icon_with_badge';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mapStateToProps = state => ({
 | 
					const mapStateToProps = state => ({
 | 
				
			||||||
  count: state.getIn(['local_settings', 'notifications', 'tab_badge']) ? state.getIn(['notifications', 'unread']) : 0,
 | 
					  count: state.getIn(['local_settings', 'notifications', 'tab_badge']) ? state.getIn(['notifications', 'unread']) : 0,
 | 
				
			||||||
 | 
					  issueBadge: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) !== 'granted',
 | 
				
			||||||
  id: 'bell',
 | 
					  id: 'bell',
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,8 @@ import {
 | 
				
			||||||
  NOTIFICATIONS_ENTER_CLEARING_MODE,
 | 
					  NOTIFICATIONS_ENTER_CLEARING_MODE,
 | 
				
			||||||
  NOTIFICATIONS_MARK_ALL_FOR_DELETE,
 | 
					  NOTIFICATIONS_MARK_ALL_FOR_DELETE,
 | 
				
			||||||
  NOTIFICATIONS_MARK_AS_READ,
 | 
					  NOTIFICATIONS_MARK_AS_READ,
 | 
				
			||||||
 | 
					  NOTIFICATIONS_SET_BROWSER_SUPPORT,
 | 
				
			||||||
 | 
					  NOTIFICATIONS_SET_BROWSER_PERMISSION,
 | 
				
			||||||
} from 'flavours/glitch/actions/notifications';
 | 
					} from 'flavours/glitch/actions/notifications';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ACCOUNT_BLOCK_SUCCESS,
 | 
					  ACCOUNT_BLOCK_SUCCESS,
 | 
				
			||||||
| 
						 | 
					@ -44,6 +46,8 @@ const initialState = ImmutableMap({
 | 
				
			||||||
  isLoading: false,
 | 
					  isLoading: false,
 | 
				
			||||||
  cleaningMode: false,
 | 
					  cleaningMode: false,
 | 
				
			||||||
  isTabVisible: true,
 | 
					  isTabVisible: true,
 | 
				
			||||||
 | 
					  browserSupport: false,
 | 
				
			||||||
 | 
					  browserPermission: 'default',
 | 
				
			||||||
  // notification removal mark of new notifs loaded whilst cleaningMode is true.
 | 
					  // notification removal mark of new notifs loaded whilst cleaningMode is true.
 | 
				
			||||||
  markNewForDelete: false,
 | 
					  markNewForDelete: false,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -275,6 +279,10 @@ export default function notifications(state = initialState, action) {
 | 
				
			||||||
    return action.timeline === 'home' ?
 | 
					    return action.timeline === 'home' ?
 | 
				
			||||||
      state.update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) :
 | 
					      state.update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) :
 | 
				
			||||||
      state;
 | 
					      state;
 | 
				
			||||||
 | 
					  case NOTIFICATIONS_SET_BROWSER_SUPPORT:
 | 
				
			||||||
 | 
					    return state.set('browserSupport', action.value);
 | 
				
			||||||
 | 
					  case NOTIFICATIONS_SET_BROWSER_PERMISSION:
 | 
				
			||||||
 | 
					    return state.set('browserPermission', action.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case NOTIFICATION_MARK_FOR_DELETE:
 | 
					  case NOTIFICATION_MARK_FOR_DELETE:
 | 
				
			||||||
    return markForDelete(state, action.id, action.yes);
 | 
					    return markForDelete(state, action.id, action.yes);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,12 +33,12 @@ const initialState = ImmutableMap({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  notifications: ImmutableMap({
 | 
					  notifications: ImmutableMap({
 | 
				
			||||||
    alerts: ImmutableMap({
 | 
					    alerts: ImmutableMap({
 | 
				
			||||||
      follow: true,
 | 
					      follow: false,
 | 
				
			||||||
      follow_request: false,
 | 
					      follow_request: false,
 | 
				
			||||||
      favourite: true,
 | 
					      favourite: false,
 | 
				
			||||||
      reblog: true,
 | 
					      reblog: false,
 | 
				
			||||||
      mention: true,
 | 
					      mention: false,
 | 
				
			||||||
      poll: true,
 | 
					      poll: false,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    quickFilter: ImmutableMap({
 | 
					    quickFilter: ImmutableMap({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -463,6 +463,15 @@
 | 
				
			||||||
  flex: 1;
 | 
					  flex: 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.column-header__issue-btn {
 | 
				
			||||||
 | 
					  color: $warning-red;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &:hover {
 | 
				
			||||||
 | 
					    color: $error-red;
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.column-header__icon {
 | 
					.column-header__icon {
 | 
				
			||||||
  display: inline-block;
 | 
					  display: inline-block;
 | 
				
			||||||
  margin-right: 5px;
 | 
					  margin-right: 5px;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -708,6 +708,17 @@
 | 
				
			||||||
    line-height: 14px;
 | 
					    line-height: 14px;
 | 
				
			||||||
    color: $primary-text-color;
 | 
					    color: $primary-text-color;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &__issue-badge {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    left: 11px;
 | 
				
			||||||
 | 
					    bottom: 1px;
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    background: $error-red;
 | 
				
			||||||
 | 
					    border-radius: 50%;
 | 
				
			||||||
 | 
					    width: 0.625rem;
 | 
				
			||||||
 | 
					    height: 0.625rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.column-link--transparent .icon-with-badge__badge {
 | 
					.column-link--transparent .icon-with-badge__badge {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import * as registerPushNotifications from 'flavours/glitch/actions/push_notifications';
 | 
					import * as registerPushNotifications from 'flavours/glitch/actions/push_notifications';
 | 
				
			||||||
 | 
					import { setupBrowserNotifications } from 'flavours/glitch/actions/notifications';
 | 
				
			||||||
import { default as Mastodon, store } from 'flavours/glitch/containers/mastodon';
 | 
					import { default as Mastodon, store } from 'flavours/glitch/containers/mastodon';
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import ReactDOM from 'react-dom';
 | 
					import ReactDOM from 'react-dom';
 | 
				
			||||||
| 
						 | 
					@ -22,6 +23,7 @@ function main() {
 | 
				
			||||||
    const props = JSON.parse(mountNode.getAttribute('data-props'));
 | 
					    const props = JSON.parse(mountNode.getAttribute('data-props'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ReactDOM.render(<Mastodon {...props} />, mountNode);
 | 
					    ReactDOM.render(<Mastodon {...props} />, mountNode);
 | 
				
			||||||
 | 
					    store.dispatch(setupBrowserNotifications());
 | 
				
			||||||
    if (process.env.NODE_ENV === 'production') {
 | 
					    if (process.env.NODE_ENV === 'production') {
 | 
				
			||||||
      // avoid offline in dev mode because it's harder to debug
 | 
					      // avoid offline in dev mode because it's harder to debug
 | 
				
			||||||
      require('offline-plugin/runtime').install();
 | 
					      require('offline-plugin/runtime').install();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										29
									
								
								app/javascript/flavours/glitch/util/notifications.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								app/javascript/flavours/glitch/util/notifications.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					// Handles browser quirks, based on
 | 
				
			||||||
 | 
					// https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checkNotificationPromise = () => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    Notification.requestPermission().then();
 | 
				
			||||||
 | 
					  } catch(e) {
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handlePermission = (permission, callback) => {
 | 
				
			||||||
 | 
					  // Whatever the user answers, we make sure Chrome stores the information
 | 
				
			||||||
 | 
					  if(!('permission' in Notification)) {
 | 
				
			||||||
 | 
					    Notification.permission = permission;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  callback(Notification.permission);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const requestNotificationPermission = (callback) => {
 | 
				
			||||||
 | 
					  if (checkNotificationPromise()) {
 | 
				
			||||||
 | 
					    Notification.requestPermission().then((permission) => handlePermission(permission, callback));
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    Notification.requestPermission((permission) => handlePermission(permission, callback));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
		Loading…
	
		Reference in a new issue