[Glitch] Add notification quick-filter bar in the frontend app
Port 13dce126655f856f23d02373fa2e333e74bdc36e to glitch-soc
This commit is contained in:
		
							parent
							
								
									a18a46ca6e
								
							
						
					
					
						commit
						06a7c07eda
					
				@ -2,6 +2,7 @@ import api, { getLinks } from 'flavours/glitch/util/api';
 | 
			
		||||
import IntlMessageFormat from 'intl-messageformat';
 | 
			
		||||
import { fetchRelationships } from './accounts';
 | 
			
		||||
import { defineMessages } from 'react-intl';
 | 
			
		||||
import { List as ImmutableList } from 'immutable';
 | 
			
		||||
import { unescapeHTML } from 'flavours/glitch/util/html';
 | 
			
		||||
import { getFilters, regexFromFilters } from 'flavours/glitch/selectors';
 | 
			
		||||
 | 
			
		||||
@ -22,6 +23,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_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
 | 
			
		||||
 | 
			
		||||
export const NOTIFICATIONS_CLEAR      = 'NOTIFICATIONS_CLEAR';
 | 
			
		||||
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
 | 
			
		||||
 | 
			
		||||
@ -84,10 +87,16 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
 | 
			
		||||
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const excludeTypesFromFilter = filter => {
 | 
			
		||||
  const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention']);
 | 
			
		||||
  return allTypes.filterNot(item => item === filter).toJS();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const noOp = () => {};
 | 
			
		||||
 | 
			
		||||
export function expandNotifications({ maxId } = {}, done = noOp) {
 | 
			
		||||
  return (dispatch, getState) => {
 | 
			
		||||
    const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']);
 | 
			
		||||
    const notifications = getState().get('notifications');
 | 
			
		||||
    const isLoadingMore = !!maxId;
 | 
			
		||||
 | 
			
		||||
@ -98,7 +107,9 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
 | 
			
		||||
 | 
			
		||||
    const params = {
 | 
			
		||||
      max_id: maxId,
 | 
			
		||||
      exclude_types: excludeTypesFromSettings(getState()),
 | 
			
		||||
      exclude_types: activeFilter === 'all'
 | 
			
		||||
        ? excludeTypesFromSettings(getState())
 | 
			
		||||
        : excludeTypesFromFilter(activeFilter),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (!maxId && notifications.get('items').size > 0) {
 | 
			
		||||
@ -244,3 +255,14 @@ export function notificationsSetVisibility(visibility) {
 | 
			
		||||
    visibility: visibility,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function setFilter (filterType) {
 | 
			
		||||
  return dispatch => {
 | 
			
		||||
    dispatch({
 | 
			
		||||
      type: NOTIFICATIONS_FILTER_SET,
 | 
			
		||||
      path: ['notifications', 'quickFilter', 'active'],
 | 
			
		||||
      value: filterType,
 | 
			
		||||
    });
 | 
			
		||||
    dispatch(expandNotifications());
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -21,9 +21,11 @@ export default class ColumnSettings extends React.PureComponent {
 | 
			
		||||
  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 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 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' />;
 | 
			
		||||
@ -35,6 +37,16 @@ export default class ColumnSettings extends React.PureComponent {
 | 
			
		||||
          <ClearColumnButton onClick={onClear} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div role='group' aria-labelledby='notifications-filter-bar'>
 | 
			
		||||
          <span id='notifications-filter-bar' className='column-settings__section'>
 | 
			
		||||
            <FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' />
 | 
			
		||||
          </span>
 | 
			
		||||
          <div className='column-settings__row'>
 | 
			
		||||
            <SettingToggle id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'show']} onChange={onChange} label={filterShowStr} />
 | 
			
		||||
            <SettingToggle id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'advanced']} onChange={onChange} label={filterAdvancedStr} />
 | 
			
		||||
          </div>
 | 
			
		||||
        </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>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,93 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 | 
			
		||||
 | 
			
		||||
const tooltips = defineMessages({
 | 
			
		||||
  mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
 | 
			
		||||
  favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' },
 | 
			
		||||
  boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
 | 
			
		||||
  follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default @injectIntl
 | 
			
		||||
class FilterBar extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    selectFilter: PropTypes.func.isRequired,
 | 
			
		||||
    selectedFilter: PropTypes.string.isRequired,
 | 
			
		||||
    advancedMode: PropTypes.bool.isRequired,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  onClick (notificationType) {
 | 
			
		||||
    return () => this.props.selectFilter(notificationType);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { selectedFilter, advancedMode, intl } = this.props;
 | 
			
		||||
    const renderedElement = !advancedMode ? (
 | 
			
		||||
      <div className='notification__filter-bar'>
 | 
			
		||||
        <button
 | 
			
		||||
          className={selectedFilter === 'all' ? 'active' : ''}
 | 
			
		||||
          onClick={this.onClick('all')}
 | 
			
		||||
        >
 | 
			
		||||
          <FormattedMessage
 | 
			
		||||
            id='notifications.filter.all'
 | 
			
		||||
            defaultMessage='All'
 | 
			
		||||
          />
 | 
			
		||||
        </button>
 | 
			
		||||
        <button
 | 
			
		||||
          className={selectedFilter === 'mention' ? 'active' : ''}
 | 
			
		||||
          onClick={this.onClick('mention')}
 | 
			
		||||
        >
 | 
			
		||||
          <FormattedMessage
 | 
			
		||||
            id='notifications.filter.mentions'
 | 
			
		||||
            defaultMessage='Mentions'
 | 
			
		||||
          />
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    ) : (
 | 
			
		||||
      <div className='notification__filter-bar'>
 | 
			
		||||
        <button
 | 
			
		||||
          className={selectedFilter === 'all' ? 'active' : ''}
 | 
			
		||||
          onClick={this.onClick('all')}
 | 
			
		||||
        >
 | 
			
		||||
          <FormattedMessage
 | 
			
		||||
            id='notifications.filter.all'
 | 
			
		||||
            defaultMessage='All'
 | 
			
		||||
          />
 | 
			
		||||
        </button>
 | 
			
		||||
        <button
 | 
			
		||||
          className={selectedFilter === 'mention' ? 'active' : ''}
 | 
			
		||||
          onClick={this.onClick('mention')}
 | 
			
		||||
          title={intl.formatMessage(tooltips.mentions)}
 | 
			
		||||
        >
 | 
			
		||||
          <i className='fa fa-fw fa-at' />
 | 
			
		||||
        </button>
 | 
			
		||||
        <button
 | 
			
		||||
          className={selectedFilter === 'favourite' ? 'active' : ''}
 | 
			
		||||
          onClick={this.onClick('favourite')}
 | 
			
		||||
          title={intl.formatMessage(tooltips.favourites)}
 | 
			
		||||
        >
 | 
			
		||||
          <i className='fa fa-fw fa-star' />
 | 
			
		||||
        </button>
 | 
			
		||||
        <button
 | 
			
		||||
          className={selectedFilter === 'reblog' ? 'active' : ''}
 | 
			
		||||
          onClick={this.onClick('reblog')}
 | 
			
		||||
          title={intl.formatMessage(tooltips.boosts)}
 | 
			
		||||
        >
 | 
			
		||||
          <i className='fa fa-fw fa-retweet' />
 | 
			
		||||
        </button>
 | 
			
		||||
        <button
 | 
			
		||||
          className={selectedFilter === 'follow' ? 'active' : ''}
 | 
			
		||||
          onClick={this.onClick('follow')}
 | 
			
		||||
          title={intl.formatMessage(tooltips.follows)}
 | 
			
		||||
        >
 | 
			
		||||
          <i className='fa fa-fw fa-user-plus' />
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
    return renderedElement;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -2,6 +2,7 @@ import { connect } from 'react-redux';
 | 
			
		||||
import { defineMessages, injectIntl } from 'react-intl';
 | 
			
		||||
import ColumnSettings from '../components/column_settings';
 | 
			
		||||
import { changeSetting } from 'flavours/glitch/actions/settings';
 | 
			
		||||
import { setFilter } from 'flavours/glitch/actions/notifications';
 | 
			
		||||
import { clearNotifications } from 'flavours/glitch/actions/notifications';
 | 
			
		||||
import { changeAlerts as changePushNotifications } from 'flavours/glitch/actions/push_notifications';
 | 
			
		||||
import { openModal } from 'flavours/glitch/actions/modal';
 | 
			
		||||
@ -21,6 +22,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 | 
			
		||||
  onChange (path, checked) {
 | 
			
		||||
    if (path[0] === 'push') {
 | 
			
		||||
      dispatch(changePushNotifications(path.slice(1), checked));
 | 
			
		||||
    } else if (path[0] === 'quickFilter') {
 | 
			
		||||
      dispatch(changeSetting(['notifications', ...path], checked));
 | 
			
		||||
      dispatch(setFilter('all'));
 | 
			
		||||
    } else {
 | 
			
		||||
      dispatch(changeSetting(['notifications', ...path], checked));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,16 @@
 | 
			
		||||
import { connect } from 'react-redux';
 | 
			
		||||
import FilterBar from '../components/filter_bar';
 | 
			
		||||
import { setFilter } from '../../../actions/notifications';
 | 
			
		||||
 | 
			
		||||
const makeMapStateToProps = state => ({
 | 
			
		||||
  selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
 | 
			
		||||
  advancedMode: state.getIn(['settings', 'notifications', 'quickFilter', 'advanced']),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const mapDispatchToProps = (dispatch) => ({
 | 
			
		||||
  selectFilter (newActiveFilter) {
 | 
			
		||||
    dispatch(setFilter(newActiveFilter));
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default connect(makeMapStateToProps, mapDispatchToProps)(FilterBar);
 | 
			
		||||
@ -15,6 +15,7 @@ import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/col
 | 
			
		||||
import NotificationContainer from './containers/notification_container';
 | 
			
		||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 | 
			
		||||
import ColumnSettingsContainer from './containers/column_settings_container';
 | 
			
		||||
import FilterBarContainer from './containers/filter_bar_container';
 | 
			
		||||
import { createSelector } from 'reselect';
 | 
			
		||||
import { List as ImmutableList } from 'immutable';
 | 
			
		||||
import { debounce } from 'lodash';
 | 
			
		||||
@ -26,11 +27,22 @@ const messages = defineMessages({
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const getNotifications = createSelector([
 | 
			
		||||
  state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
 | 
			
		||||
  state => state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
 | 
			
		||||
  state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
 | 
			
		||||
  state => state.getIn(['notifications', 'items']),
 | 
			
		||||
], (excludedTypes, notifications) => notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type'))));
 | 
			
		||||
], (showFilterBar, allowedType, excludedTypes, notifications) => {
 | 
			
		||||
  if (!showFilterBar || allowedType === 'all') {
 | 
			
		||||
    // used if user changed the notification settings after loading the notifications from the server
 | 
			
		||||
    // otherwise a list of notifications will come pre-filtered from the backend
 | 
			
		||||
    // we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category
 | 
			
		||||
    return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')));
 | 
			
		||||
  }
 | 
			
		||||
  return notifications.filter(item => item !== null && allowedType === item.get('type'));
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = state => ({
 | 
			
		||||
  showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
 | 
			
		||||
  notifications: getNotifications(state),
 | 
			
		||||
  localSettings:  state.get('local_settings'),
 | 
			
		||||
  isLoading: state.getIn(['notifications', 'isLoading'], true),
 | 
			
		||||
@ -60,6 +72,7 @@ export default class Notifications extends React.PureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    columnId: PropTypes.string,
 | 
			
		||||
    notifications: ImmutablePropTypes.list.isRequired,
 | 
			
		||||
    showFilterBar: PropTypes.bool.isRequired,
 | 
			
		||||
    dispatch: PropTypes.func.isRequired,
 | 
			
		||||
    shouldUpdateScroll: PropTypes.func,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
@ -151,12 +164,16 @@ export default class Notifications extends React.PureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore } = this.props;
 | 
			
		||||
    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, showFilterBar } = this.props;
 | 
			
		||||
    const pinned = !!columnId;
 | 
			
		||||
    const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
 | 
			
		||||
 | 
			
		||||
    let scrollableContent = null;
 | 
			
		||||
 | 
			
		||||
    const filterBarContainer = showFilterBar
 | 
			
		||||
      ? (<FilterBarContainer />)
 | 
			
		||||
      : null;
 | 
			
		||||
 | 
			
		||||
    if (isLoading && this.scrollableContent) {
 | 
			
		||||
      scrollableContent = this.scrollableContent;
 | 
			
		||||
    } else if (notifications.size > 0 || hasMore) {
 | 
			
		||||
@ -222,7 +239,7 @@ export default class Notifications extends React.PureComponent {
 | 
			
		||||
        >
 | 
			
		||||
          <ColumnSettingsContainer />
 | 
			
		||||
        </ColumnHeader>
 | 
			
		||||
 | 
			
		||||
        {filterBarContainer}
 | 
			
		||||
        {scrollContainer}
 | 
			
		||||
      </Column>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import {
 | 
			
		||||
  NOTIFICATIONS_EXPAND_SUCCESS,
 | 
			
		||||
  NOTIFICATIONS_EXPAND_REQUEST,
 | 
			
		||||
  NOTIFICATIONS_EXPAND_FAIL,
 | 
			
		||||
  NOTIFICATIONS_FILTER_SET,
 | 
			
		||||
  NOTIFICATIONS_CLEAR,
 | 
			
		||||
  NOTIFICATIONS_SCROLL_TOP,
 | 
			
		||||
  NOTIFICATIONS_DELETE_MARKED_REQUEST,
 | 
			
		||||
@ -197,6 +198,8 @@ export default function notifications(state = initialState, action) {
 | 
			
		||||
  case NOTIFICATIONS_DELETE_MARKED_FAIL:
 | 
			
		||||
  case NOTIFICATIONS_EXPAND_FAIL:
 | 
			
		||||
    return state.set('isLoading', false);
 | 
			
		||||
  case NOTIFICATIONS_FILTER_SET:
 | 
			
		||||
    return state.set('items', ImmutableList()).set('hasMore', true);
 | 
			
		||||
  case NOTIFICATIONS_SCROLL_TOP:
 | 
			
		||||
    return updateTop(state, action.top);
 | 
			
		||||
  case NOTIFICATIONS_UPDATE:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										245
									
								
								app/javascript/flavours/glitch/reducers/notifications.js.orig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								app/javascript/flavours/glitch/reducers/notifications.js.orig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,245 @@
 | 
			
		||||
import {
 | 
			
		||||
  NOTIFICATIONS_MOUNT,
 | 
			
		||||
  NOTIFICATIONS_UNMOUNT,
 | 
			
		||||
  NOTIFICATIONS_SET_VISIBILITY,
 | 
			
		||||
  NOTIFICATIONS_UPDATE,
 | 
			
		||||
  NOTIFICATIONS_EXPAND_SUCCESS,
 | 
			
		||||
  NOTIFICATIONS_EXPAND_REQUEST,
 | 
			
		||||
  NOTIFICATIONS_EXPAND_FAIL,
 | 
			
		||||
  NOTIFICATIONS_CLEAR,
 | 
			
		||||
  NOTIFICATIONS_SCROLL_TOP,
 | 
			
		||||
  NOTIFICATIONS_DELETE_MARKED_REQUEST,
 | 
			
		||||
  NOTIFICATIONS_DELETE_MARKED_SUCCESS,
 | 
			
		||||
  NOTIFICATION_MARK_FOR_DELETE,
 | 
			
		||||
  NOTIFICATIONS_DELETE_MARKED_FAIL,
 | 
			
		||||
  NOTIFICATIONS_ENTER_CLEARING_MODE,
 | 
			
		||||
  NOTIFICATIONS_MARK_ALL_FOR_DELETE,
 | 
			
		||||
} from 'flavours/glitch/actions/notifications';
 | 
			
		||||
import {
 | 
			
		||||
  ACCOUNT_BLOCK_SUCCESS,
 | 
			
		||||
  ACCOUNT_MUTE_SUCCESS,
 | 
			
		||||
} from 'flavours/glitch/actions/accounts';
 | 
			
		||||
import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from 'flavours/glitch/actions/timelines';
 | 
			
		||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 | 
			
		||||
import compareId from 'flavours/glitch/util/compare_id';
 | 
			
		||||
 | 
			
		||||
const initialState = ImmutableMap({
 | 
			
		||||
  items: ImmutableList(),
 | 
			
		||||
  hasMore: true,
 | 
			
		||||
  top: true,
 | 
			
		||||
  mounted: 0,
 | 
			
		||||
  unread: 0,
 | 
			
		||||
  lastReadId: '0',
 | 
			
		||||
  isLoading: false,
 | 
			
		||||
  cleaningMode: false,
 | 
			
		||||
  isTabVisible: true,
 | 
			
		||||
  // notification removal mark of new notifs loaded whilst cleaningMode is true.
 | 
			
		||||
  markNewForDelete: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const notificationToMap = (state, notification) => ImmutableMap({
 | 
			
		||||
  id: notification.id,
 | 
			
		||||
  type: notification.type,
 | 
			
		||||
  account: notification.account.id,
 | 
			
		||||
  markedForDelete: state.get('markNewForDelete'),
 | 
			
		||||
  status: notification.status ? notification.status.id : null,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const normalizeNotification = (state, notification) => {
 | 
			
		||||
  const top = !shouldCountUnreadNotifications(state);
 | 
			
		||||
 | 
			
		||||
  if (top) {
 | 
			
		||||
    state = state.set('lastReadId', notification.id);
 | 
			
		||||
  } else {
 | 
			
		||||
    state = state.update('unread', unread => unread + 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return state.update('items', list => {
 | 
			
		||||
    if (top && list.size > 40) {
 | 
			
		||||
      list = list.take(20);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return list.unshift(notificationToMap(state, notification));
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const expandNormalizedNotifications = (state, notifications, next) => {
 | 
			
		||||
  const top = !(shouldCountUnreadNotifications(state));
 | 
			
		||||
  const lastReadId = state.get('lastReadId');
 | 
			
		||||
  let items = ImmutableList();
 | 
			
		||||
 | 
			
		||||
  notifications.forEach((n, i) => {
 | 
			
		||||
    items = items.set(i, notificationToMap(state, n));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return state.withMutations(mutable => {
 | 
			
		||||
    if (!items.isEmpty()) {
 | 
			
		||||
      mutable.update('items', list => {
 | 
			
		||||
        const lastIndex = 1 + list.findLastIndex(
 | 
			
		||||
          item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id'))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const firstIndex = 1 + list.take(lastIndex).findLastIndex(
 | 
			
		||||
          item => item !== null && compareId(item.get('id'), items.first().get('id')) > 0
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return list.take(firstIndex).concat(items, list.skip(lastIndex));
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (top) {
 | 
			
		||||
      if (!items.isEmpty()) {
 | 
			
		||||
        mutable.update('lastReadId', id => compareId(id, items.first().get('id')) > 0 ? id : items.first().get('id'));
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      mutable.update('unread', unread => unread + items.filter(item => compareId(item.get('id'), lastReadId) > 0).size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!next) {
 | 
			
		||||
      mutable.set('hasMore', false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mutable.set('isLoading', false);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const filterNotifications = (state, relationship) => {
 | 
			
		||||
  return state.update('items', list => list.filterNot(item => item !== null && item.get('account') === relationship.id));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const clearUnread = (state) => {
 | 
			
		||||
  state = state.set('unread', 0);
 | 
			
		||||
  const lastNotification = state.get('items').find(item => item !== null);
 | 
			
		||||
  return state.set('lastReadId', lastNotification ? lastNotification.get('id') : '0');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const updateTop = (state, top) => {
 | 
			
		||||
  state = state.set('top', top);
 | 
			
		||||
 | 
			
		||||
  if (!shouldCountUnreadNotifications(state)) {
 | 
			
		||||
    state = clearUnread(state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return state.set('top', top);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const deleteByStatus = (state, statusId) => {
 | 
			
		||||
  const top = !(shouldCountUnreadNotifications(state));
 | 
			
		||||
  if (!top) {
 | 
			
		||||
    const lastReadId = state.get('lastReadId');
 | 
			
		||||
    const deletedUnread = state.get('items').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0);
 | 
			
		||||
    state = state.update('unread', unread => unread - deletedUnread.size);
 | 
			
		||||
  }
 | 
			
		||||
  return state.update('items', list => list.filterNot(item => item !== null && item.get('status') === statusId));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const markForDelete = (state, notificationId, yes) => {
 | 
			
		||||
  return state.update('items', list => list.map(item => {
 | 
			
		||||
    if(item.get('id') === notificationId) {
 | 
			
		||||
      return item.set('markedForDelete', yes);
 | 
			
		||||
    } else {
 | 
			
		||||
      return item;
 | 
			
		||||
    }
 | 
			
		||||
  }));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const markAllForDelete = (state, yes) => {
 | 
			
		||||
  return state.update('items', list => list.map(item => {
 | 
			
		||||
    if(yes !== null) {
 | 
			
		||||
      return item.set('markedForDelete', yes);
 | 
			
		||||
    } else {
 | 
			
		||||
      return item.set('markedForDelete', !item.get('markedForDelete'));
 | 
			
		||||
    }
 | 
			
		||||
  }));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const unmarkAllForDelete = (state) => {
 | 
			
		||||
  return state.update('items', list => list.map(item => item.set('markedForDelete', false)));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const deleteMarkedNotifs = (state) => {
 | 
			
		||||
  return state.update('items', list => list.filterNot(item => item.get('markedForDelete')));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const updateMounted = (state) => {
 | 
			
		||||
  state = state.update('mounted', count => count + 1);
 | 
			
		||||
  if (!shouldCountUnreadNotifications(state)) {
 | 
			
		||||
    state = clearUnread(state);
 | 
			
		||||
  }
 | 
			
		||||
  return state;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const updateVisibility = (state, visibility) => {
 | 
			
		||||
  state = state.set('isTabVisible', visibility);
 | 
			
		||||
  if (!shouldCountUnreadNotifications(state)) {
 | 
			
		||||
    state = clearUnread(state);
 | 
			
		||||
  }
 | 
			
		||||
  return state;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const shouldCountUnreadNotifications = (state) => {
 | 
			
		||||
  return !(state.get('isTabVisible') && state.get('top') && state.get('mounted') > 0);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function notifications(state = initialState, action) {
 | 
			
		||||
  let st;
 | 
			
		||||
 | 
			
		||||
  switch(action.type) {
 | 
			
		||||
  case NOTIFICATIONS_MOUNT:
 | 
			
		||||
    return updateMounted(state);
 | 
			
		||||
  case NOTIFICATIONS_UNMOUNT:
 | 
			
		||||
    return state.update('mounted', count => count - 1);
 | 
			
		||||
  case NOTIFICATIONS_SET_VISIBILITY:
 | 
			
		||||
    return updateVisibility(state, action.visibility);
 | 
			
		||||
  case NOTIFICATIONS_EXPAND_REQUEST:
 | 
			
		||||
  case NOTIFICATIONS_DELETE_MARKED_REQUEST:
 | 
			
		||||
    return state.set('isLoading', true);
 | 
			
		||||
  case NOTIFICATIONS_DELETE_MARKED_FAIL:
 | 
			
		||||
  case NOTIFICATIONS_EXPAND_FAIL:
 | 
			
		||||
    return state.set('isLoading', false);
 | 
			
		||||
  case NOTIFICATIONS_SCROLL_TOP:
 | 
			
		||||
    return updateTop(state, action.top);
 | 
			
		||||
  case NOTIFICATIONS_UPDATE:
 | 
			
		||||
    return normalizeNotification(state, action.notification);
 | 
			
		||||
  case NOTIFICATIONS_EXPAND_SUCCESS:
 | 
			
		||||
    return expandNormalizedNotifications(state, action.notifications, action.next);
 | 
			
		||||
  case ACCOUNT_BLOCK_SUCCESS:
 | 
			
		||||
  case ACCOUNT_MUTE_SUCCESS:
 | 
			
		||||
    return filterNotifications(state, action.relationship);
 | 
			
		||||
  case NOTIFICATIONS_CLEAR:
 | 
			
		||||
    return state.set('items', ImmutableList()).set('hasMore', false);
 | 
			
		||||
  case TIMELINE_DELETE:
 | 
			
		||||
    return deleteByStatus(state, action.id);
 | 
			
		||||
  case TIMELINE_DISCONNECT:
 | 
			
		||||
    return action.timeline === 'home' ?
 | 
			
		||||
      state.update('items', items => items.first() ? items.unshift(null) : items) :
 | 
			
		||||
      state;
 | 
			
		||||
 | 
			
		||||
  case NOTIFICATION_MARK_FOR_DELETE:
 | 
			
		||||
    return markForDelete(state, action.id, action.yes);
 | 
			
		||||
 | 
			
		||||
  case NOTIFICATIONS_DELETE_MARKED_SUCCESS:
 | 
			
		||||
    return deleteMarkedNotifs(state).set('isLoading', false);
 | 
			
		||||
 | 
			
		||||
  case NOTIFICATIONS_ENTER_CLEARING_MODE:
 | 
			
		||||
    st = state.set('cleaningMode', action.yes);
 | 
			
		||||
    if (!action.yes) {
 | 
			
		||||
      return unmarkAllForDelete(st).set('markNewForDelete', false);
 | 
			
		||||
    } else {
 | 
			
		||||
      return st;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  case NOTIFICATIONS_MARK_ALL_FOR_DELETE:
 | 
			
		||||
    st = state;
 | 
			
		||||
    if (action.yes === null) {
 | 
			
		||||
      // Toggle - this is a bit confusing, as it toggles the all-none mode
 | 
			
		||||
      //st = st.set('markNewForDelete', !st.get('markNewForDelete'));
 | 
			
		||||
    } else {
 | 
			
		||||
      st = st.set('markNewForDelete', action.yes);
 | 
			
		||||
    }
 | 
			
		||||
    return markAllForDelete(st, action.yes);
 | 
			
		||||
 | 
			
		||||
  default:
 | 
			
		||||
    return state;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import { SETTING_CHANGE, SETTING_SAVE } from 'flavours/glitch/actions/settings';
 | 
			
		||||
import { NOTIFICATIONS_FILTER_SET } from 'flavours/glitch/actions/notifications';
 | 
			
		||||
import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE } from 'flavours/glitch/actions/columns';
 | 
			
		||||
import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
 | 
			
		||||
import { EMOJI_USE } from 'flavours/glitch/actions/emojis';
 | 
			
		||||
@ -34,6 +35,12 @@ const initialState = ImmutableMap({
 | 
			
		||||
      mention: true,
 | 
			
		||||
    }),
 | 
			
		||||
 | 
			
		||||
    quickFilter: ImmutableMap({
 | 
			
		||||
      active: 'all',
 | 
			
		||||
      show: true,
 | 
			
		||||
      advanced: false,
 | 
			
		||||
    }),
 | 
			
		||||
 | 
			
		||||
    shows: ImmutableMap({
 | 
			
		||||
      follow: true,
 | 
			
		||||
      favourite: true,
 | 
			
		||||
@ -99,6 +106,7 @@ export default function settings(state = initialState, action) {
 | 
			
		||||
  switch(action.type) {
 | 
			
		||||
  case STORE_HYDRATE:
 | 
			
		||||
    return hydrate(state, action.state.get('settings'));
 | 
			
		||||
  case NOTIFICATIONS_FILTER_SET:
 | 
			
		||||
  case SETTING_CHANGE:
 | 
			
		||||
    return state
 | 
			
		||||
      .setIn(action.path, action.value)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user