Add notification quick-filter bar in the frontend app (#9399)
* create FilterBar componer and its container, unstyled * introduce basic styling for FilterBar * add selection css * allow FilterBar to display active CSS with js * connect the FilterBar to the Redux state * change getNotifications to use filter * remove temporary comments * add an option to turn the FilterBar off in settings * fix showFilterBar data type to boolean * fix eslint errors * add English and Polish translations * allowed filter bar overflow to accomodate for longer languages * fix mispelled translation key * add unified CSS look * replace text in FilterBar with icons * add tooltips * replace text @ with an icon * introduce simple and advanced filtering view * add ability to toggle the advanced view * add Polish translations * change Advanced View description to be more clear * make each filter flush notifications and load new ones, fixing pagination * simplify getNotifications once frontend filtering is not needed for FilterBar * add a semicolon * Revert "simplify getNotifications once frontend filtering is not needed for FilterBar" This reverts commit 9f4be7857135b0327814bd22a3e8a4e7b546f7cc. * reset filter to 'all' when turning off FilterBar
This commit is contained in:
		
							parent
							
								
									5f0d3e8bad
								
							
						
					
					
						commit
						13dce12665
					
				
					 11 changed files with 244 additions and 7 deletions
				
			
		| 
						 | 
					@ -8,6 +8,7 @@ import {
 | 
				
			||||||
  importFetchedStatuses,
 | 
					  importFetchedStatuses,
 | 
				
			||||||
} from './importer';
 | 
					} from './importer';
 | 
				
			||||||
import { defineMessages } from 'react-intl';
 | 
					import { defineMessages } from 'react-intl';
 | 
				
			||||||
 | 
					import { List as ImmutableList } from 'immutable';
 | 
				
			||||||
import { unescapeHTML } from '../utils/html';
 | 
					import { unescapeHTML } from '../utils/html';
 | 
				
			||||||
import { getFilters, regexFromFilters } from '../selectors';
 | 
					import { getFilters, regexFromFilters } from '../selectors';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +19,8 @@ export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
 | 
				
			||||||
export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
 | 
					export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
 | 
				
			||||||
export const NOTIFICATIONS_EXPAND_FAIL    = 'NOTIFICATIONS_EXPAND_FAIL';
 | 
					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_CLEAR      = 'NOTIFICATIONS_CLEAR';
 | 
				
			||||||
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
 | 
					export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -88,10 +91,16 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
 | 
					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 = () => {};
 | 
					const noOp = () => {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function expandNotifications({ maxId } = {}, done = noOp) {
 | 
					export function expandNotifications({ maxId } = {}, done = noOp) {
 | 
				
			||||||
  return (dispatch, getState) => {
 | 
					  return (dispatch, getState) => {
 | 
				
			||||||
 | 
					    const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']);
 | 
				
			||||||
    const notifications = getState().get('notifications');
 | 
					    const notifications = getState().get('notifications');
 | 
				
			||||||
    const isLoadingMore = !!maxId;
 | 
					    const isLoadingMore = !!maxId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,7 +111,9 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const params = {
 | 
					    const params = {
 | 
				
			||||||
      max_id: maxId,
 | 
					      max_id: maxId,
 | 
				
			||||||
      exclude_types: excludeTypesFromSettings(getState()),
 | 
					      exclude_types: activeFilter === 'all'
 | 
				
			||||||
 | 
					        ? excludeTypesFromSettings(getState())
 | 
				
			||||||
 | 
					        : excludeTypesFromFilter(activeFilter),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!maxId && notifications.get('items').size > 0) {
 | 
					    if (!maxId && notifications.get('items').size > 0) {
 | 
				
			||||||
| 
						 | 
					@ -167,3 +178,14 @@ export function scrollTopNotifications(top) {
 | 
				
			||||||
    top,
 | 
					    top,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 () {
 | 
					  render () {
 | 
				
			||||||
    const { settings, pushSettings, onChange, onClear } = this.props;
 | 
					    const { settings, pushSettings, onChange, onClear } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
 | 
					    const filterShowStr = <FormattedMessage id='notifications.column_settings.filter_bar.show' defaultMessage='Show' />;
 | 
				
			||||||
    const showStr  = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
 | 
					    const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />;
 | 
				
			||||||
    const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
 | 
					    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 showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
 | 
				
			||||||
    const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
 | 
					    const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
 | 
				
			||||||
| 
						 | 
					@ -34,6 +36,16 @@ export default class ColumnSettings extends React.PureComponent {
 | 
				
			||||||
          <ClearColumnButton onClick={onClear} />
 | 
					          <ClearColumnButton onClick={onClear} />
 | 
				
			||||||
        </div>
 | 
					        </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'>
 | 
					        <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>
 | 
					          <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 { defineMessages, injectIntl } from 'react-intl';
 | 
				
			||||||
import ColumnSettings from '../components/column_settings';
 | 
					import ColumnSettings from '../components/column_settings';
 | 
				
			||||||
import { changeSetting } from '../../../actions/settings';
 | 
					import { changeSetting } from '../../../actions/settings';
 | 
				
			||||||
 | 
					import { setFilter } from '../../../actions/notifications';
 | 
				
			||||||
import { clearNotifications } from '../../../actions/notifications';
 | 
					import { clearNotifications } from '../../../actions/notifications';
 | 
				
			||||||
import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications';
 | 
					import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications';
 | 
				
			||||||
import { openModal } from '../../../actions/modal';
 | 
					import { openModal } from '../../../actions/modal';
 | 
				
			||||||
| 
						 | 
					@ -21,6 +22,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 | 
				
			||||||
  onChange (path, checked) {
 | 
					  onChange (path, checked) {
 | 
				
			||||||
    if (path[0] === 'push') {
 | 
					    if (path[0] === 'push') {
 | 
				
			||||||
      dispatch(changePushNotifications(path.slice(1), checked));
 | 
					      dispatch(changePushNotifications(path.slice(1), checked));
 | 
				
			||||||
 | 
					    } else if (path[0] === 'quickFilter') {
 | 
				
			||||||
 | 
					      dispatch(changeSetting(['notifications', ...path], checked));
 | 
				
			||||||
 | 
					      dispatch(setFilter('all'));
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      dispatch(changeSetting(['notifications', ...path], checked));
 | 
					      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);
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
 | 
				
			||||||
import NotificationContainer from './containers/notification_container';
 | 
					import NotificationContainer from './containers/notification_container';
 | 
				
			||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 | 
					import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 | 
				
			||||||
import ColumnSettingsContainer from './containers/column_settings_container';
 | 
					import ColumnSettingsContainer from './containers/column_settings_container';
 | 
				
			||||||
 | 
					import FilterBarContainer from './containers/filter_bar_container';
 | 
				
			||||||
import { createSelector } from 'reselect';
 | 
					import { createSelector } from 'reselect';
 | 
				
			||||||
import { List as ImmutableList } from 'immutable';
 | 
					import { List as ImmutableList } from 'immutable';
 | 
				
			||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
| 
						 | 
					@ -20,11 +21,22 @@ const messages = defineMessages({
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getNotifications = createSelector([
 | 
					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 => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
 | 
				
			||||||
  state => state.getIn(['notifications', 'items']),
 | 
					  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 => ({
 | 
					const mapStateToProps = state => ({
 | 
				
			||||||
 | 
					  showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
 | 
				
			||||||
  notifications: getNotifications(state),
 | 
					  notifications: getNotifications(state),
 | 
				
			||||||
  isLoading: state.getIn(['notifications', 'isLoading'], true),
 | 
					  isLoading: state.getIn(['notifications', 'isLoading'], true),
 | 
				
			||||||
  isUnread: state.getIn(['notifications', 'unread']) > 0,
 | 
					  isUnread: state.getIn(['notifications', 'unread']) > 0,
 | 
				
			||||||
| 
						 | 
					@ -38,6 +50,7 @@ class Notifications extends React.PureComponent {
 | 
				
			||||||
  static propTypes = {
 | 
					  static propTypes = {
 | 
				
			||||||
    columnId: PropTypes.string,
 | 
					    columnId: PropTypes.string,
 | 
				
			||||||
    notifications: ImmutablePropTypes.list.isRequired,
 | 
					    notifications: ImmutablePropTypes.list.isRequired,
 | 
				
			||||||
 | 
					    showFilterBar: PropTypes.bool.isRequired,
 | 
				
			||||||
    dispatch: PropTypes.func.isRequired,
 | 
					    dispatch: PropTypes.func.isRequired,
 | 
				
			||||||
    shouldUpdateScroll: PropTypes.func,
 | 
					    shouldUpdateScroll: PropTypes.func,
 | 
				
			||||||
    intl: PropTypes.object.isRequired,
 | 
					    intl: PropTypes.object.isRequired,
 | 
				
			||||||
| 
						 | 
					@ -117,12 +130,16 @@ class Notifications extends React.PureComponent {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render () {
 | 
					  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 pinned = !!columnId;
 | 
				
			||||||
    const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
 | 
					    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;
 | 
					    let scrollableContent = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const filterBarContainer = showFilterBar
 | 
				
			||||||
 | 
					      ? (<FilterBarContainer />)
 | 
				
			||||||
 | 
					      : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (isLoading && this.scrollableContent) {
 | 
					    if (isLoading && this.scrollableContent) {
 | 
				
			||||||
      scrollableContent = this.scrollableContent;
 | 
					      scrollableContent = this.scrollableContent;
 | 
				
			||||||
    } else if (notifications.size > 0 || hasMore) {
 | 
					    } else if (notifications.size > 0 || hasMore) {
 | 
				
			||||||
| 
						 | 
					@ -179,7 +196,7 @@ class Notifications extends React.PureComponent {
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <ColumnSettingsContainer />
 | 
					          <ColumnSettingsContainer />
 | 
				
			||||||
        </ColumnHeader>
 | 
					        </ColumnHeader>
 | 
				
			||||||
 | 
					        {filterBarContainer}
 | 
				
			||||||
        {scrollContainer}
 | 
					        {scrollContainer}
 | 
				
			||||||
      </Column>
 | 
					      </Column>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -223,6 +223,14 @@
 | 
				
			||||||
  "notification.reblog": "{name} boosted your status",
 | 
					  "notification.reblog": "{name} boosted your status",
 | 
				
			||||||
  "notifications.clear": "Clear notifications",
 | 
					  "notifications.clear": "Clear notifications",
 | 
				
			||||||
  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
 | 
					  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
 | 
				
			||||||
 | 
					  "notifications.filter.all": "All",
 | 
				
			||||||
 | 
					  "notifications.filter.mentions": "Mentions",
 | 
				
			||||||
 | 
					  "notifications.filter.favourites": "Favourites",
 | 
				
			||||||
 | 
					  "notifications.filter.boosts": "Boosts",
 | 
				
			||||||
 | 
					  "notifications.filter.follows": "Follows",
 | 
				
			||||||
 | 
					  "notifications.column_settings.filter_bar.category": "Quick filter bar",
 | 
				
			||||||
 | 
					  "notifications.column_settings.filter_bar.show": "Show",
 | 
				
			||||||
 | 
					  "notifications.column_settings.filter_bar.advanced": "Display all categories",
 | 
				
			||||||
  "notifications.column_settings.alert": "Desktop notifications",
 | 
					  "notifications.column_settings.alert": "Desktop notifications",
 | 
				
			||||||
  "notifications.column_settings.favourite": "Favourites:",
 | 
					  "notifications.column_settings.favourite": "Favourites:",
 | 
				
			||||||
  "notifications.column_settings.follow": "New followers:",
 | 
					  "notifications.column_settings.follow": "New followers:",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -223,6 +223,14 @@
 | 
				
			||||||
  "notification.reblog": "{name} podbił(a) Twój wpis",
 | 
					  "notification.reblog": "{name} podbił(a) Twój wpis",
 | 
				
			||||||
  "notifications.clear": "Wyczyść powiadomienia",
 | 
					  "notifications.clear": "Wyczyść powiadomienia",
 | 
				
			||||||
  "notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?",
 | 
					  "notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?",
 | 
				
			||||||
 | 
					  "notifications.filter.all": "Wszystkie",
 | 
				
			||||||
 | 
					  "notifications.filter.mentions": "Wspomnienia",
 | 
				
			||||||
 | 
					  "notifications.filter.favourites": "Ulubione",
 | 
				
			||||||
 | 
					  "notifications.filter.boosts": "Podbicia",
 | 
				
			||||||
 | 
					  "notifications.filter.follows": "Śledzenia",
 | 
				
			||||||
 | 
					  "notifications.column_settings.filter_bar.category": "Szybkie filtrowanie",
 | 
				
			||||||
 | 
					  "notifications.column_settings.filter_bar.show": "Pokaż",
 | 
				
			||||||
 | 
					  "notifications.column_settings.filter_bar.advanced": "Wyświetl wszystkie kategorie",
 | 
				
			||||||
  "notifications.column_settings.alert": "Powiadomienia na pulpicie",
 | 
					  "notifications.column_settings.alert": "Powiadomienia na pulpicie",
 | 
				
			||||||
  "notifications.column_settings.favourite": "Dodanie do ulubionych:",
 | 
					  "notifications.column_settings.favourite": "Dodanie do ulubionych:",
 | 
				
			||||||
  "notifications.column_settings.follow": "Nowi śledzący:",
 | 
					  "notifications.column_settings.follow": "Nowi śledzący:",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ import {
 | 
				
			||||||
  NOTIFICATIONS_EXPAND_SUCCESS,
 | 
					  NOTIFICATIONS_EXPAND_SUCCESS,
 | 
				
			||||||
  NOTIFICATIONS_EXPAND_REQUEST,
 | 
					  NOTIFICATIONS_EXPAND_REQUEST,
 | 
				
			||||||
  NOTIFICATIONS_EXPAND_FAIL,
 | 
					  NOTIFICATIONS_EXPAND_FAIL,
 | 
				
			||||||
 | 
					  NOTIFICATIONS_FILTER_SET,
 | 
				
			||||||
  NOTIFICATIONS_CLEAR,
 | 
					  NOTIFICATIONS_CLEAR,
 | 
				
			||||||
  NOTIFICATIONS_SCROLL_TOP,
 | 
					  NOTIFICATIONS_SCROLL_TOP,
 | 
				
			||||||
} from '../actions/notifications';
 | 
					} from '../actions/notifications';
 | 
				
			||||||
| 
						 | 
					@ -98,6 +99,8 @@ export default function notifications(state = initialState, action) {
 | 
				
			||||||
    return state.set('isLoading', true);
 | 
					    return state.set('isLoading', true);
 | 
				
			||||||
  case NOTIFICATIONS_EXPAND_FAIL:
 | 
					  case NOTIFICATIONS_EXPAND_FAIL:
 | 
				
			||||||
    return state.set('isLoading', false);
 | 
					    return state.set('isLoading', false);
 | 
				
			||||||
 | 
					  case NOTIFICATIONS_FILTER_SET:
 | 
				
			||||||
 | 
					    return state.set('items', ImmutableList()).set('hasMore', true);
 | 
				
			||||||
  case NOTIFICATIONS_SCROLL_TOP:
 | 
					  case NOTIFICATIONS_SCROLL_TOP:
 | 
				
			||||||
    return updateTop(state, action.top);
 | 
					    return updateTop(state, action.top);
 | 
				
			||||||
  case NOTIFICATIONS_UPDATE:
 | 
					  case NOTIFICATIONS_UPDATE:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import { SETTING_CHANGE, SETTING_SAVE } from '../actions/settings';
 | 
					import { SETTING_CHANGE, SETTING_SAVE } from '../actions/settings';
 | 
				
			||||||
 | 
					import { NOTIFICATIONS_FILTER_SET } from '../actions/notifications';
 | 
				
			||||||
import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE, COLUMN_PARAMS_CHANGE } from '../actions/columns';
 | 
					import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE, COLUMN_PARAMS_CHANGE } from '../actions/columns';
 | 
				
			||||||
import { STORE_HYDRATE } from '../actions/store';
 | 
					import { STORE_HYDRATE } from '../actions/store';
 | 
				
			||||||
import { EMOJI_USE } from '../actions/emojis';
 | 
					import { EMOJI_USE } from '../actions/emojis';
 | 
				
			||||||
| 
						 | 
					@ -32,6 +33,12 @@ const initialState = ImmutableMap({
 | 
				
			||||||
      mention: true,
 | 
					      mention: true,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    quickFilter: ImmutableMap({
 | 
				
			||||||
 | 
					      active: 'all',
 | 
				
			||||||
 | 
					      show: true,
 | 
				
			||||||
 | 
					      advanced: false,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    shows: ImmutableMap({
 | 
					    shows: ImmutableMap({
 | 
				
			||||||
      follow: true,
 | 
					      follow: true,
 | 
				
			||||||
      favourite: true,
 | 
					      favourite: true,
 | 
				
			||||||
| 
						 | 
					@ -112,6 +119,7 @@ export default function settings(state = initialState, action) {
 | 
				
			||||||
  switch(action.type) {
 | 
					  switch(action.type) {
 | 
				
			||||||
  case STORE_HYDRATE:
 | 
					  case STORE_HYDRATE:
 | 
				
			||||||
    return hydrate(state, action.state.get('settings'));
 | 
					    return hydrate(state, action.state.get('settings'));
 | 
				
			||||||
 | 
					  case NOTIFICATIONS_FILTER_SET:
 | 
				
			||||||
  case SETTING_CHANGE:
 | 
					  case SETTING_CHANGE:
 | 
				
			||||||
    return state
 | 
					    return state
 | 
				
			||||||
      .setIn(action.path, action.value)
 | 
					      .setIn(action.path, action.value)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1484,6 +1484,52 @@ a.account__display-name {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.notification__filter-bar {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  background: $ui-base-color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  & > button {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    flex-grow: 1;
 | 
				
			||||||
 | 
					    color: $primary-text-color;
 | 
				
			||||||
 | 
					    padding: 10px 5px 12px;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    font-weight: 400;
 | 
				
			||||||
 | 
					    font-size: 15px;
 | 
				
			||||||
 | 
					    line-height: 18px;
 | 
				
			||||||
 | 
					    background: darken($ui-base-color, 4%);
 | 
				
			||||||
 | 
					    border: 0;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid lighten($ui-base-color, 8%);
 | 
				
			||||||
 | 
					    cursor: default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.active {
 | 
				
			||||||
 | 
					      color: $secondary-text-color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &::before,
 | 
				
			||||||
 | 
					      &::after {
 | 
				
			||||||
 | 
					        display: block;
 | 
				
			||||||
 | 
					        content: "";
 | 
				
			||||||
 | 
					        position: absolute;
 | 
				
			||||||
 | 
					        bottom: 0;
 | 
				
			||||||
 | 
					        left: 50%;
 | 
				
			||||||
 | 
					        width: 0;
 | 
				
			||||||
 | 
					        height: 0;
 | 
				
			||||||
 | 
					        transform: translateX(-50%);
 | 
				
			||||||
 | 
					        border-style: solid;
 | 
				
			||||||
 | 
					        border-width: 0 10px 10px;
 | 
				
			||||||
 | 
					        border-color: transparent transparent lighten($ui-base-color, 8%);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &::after {
 | 
				
			||||||
 | 
					        bottom: -1px;
 | 
				
			||||||
 | 
					        border-color: transparent transparent $ui-base-color;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.notification__message {
 | 
					.notification__message {
 | 
				
			||||||
  margin: 0 10px 0 68px;
 | 
					  margin: 0 10px 0 68px;
 | 
				
			||||||
  padding: 8px 0 0;
 | 
					  padding: 8px 0 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue