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…
	
		Reference in a new issue