Updated frontend notification filtering and configuration

• Updated:
- frontend notification filtering and configuration

• Added:
- lastReadNotificationId to initial_state
- minWidth20PX for responsive notification icon hiding on xs

• Removed:
- unused code
This commit is contained in:
mgabdev 2020-05-21 15:37:40 -04:00
parent 83696f8098
commit 663f46b166
8 changed files with 245 additions and 264 deletions

View File

@ -140,7 +140,7 @@ const excludeTypesFromFilter = filter => {
return allTypes.filterNot(item => item === filter).toJS();
};
const noOp = () => { };
const noOp = () => {}
export function expandNotifications({ maxId } = {}, done = noOp) {
return (dispatch, getState) => {
@ -252,17 +252,20 @@ export function setFilter(path, value) {
export function markReadNotifications() {
return (dispatch, getState) => {
if (!me) return;
const top_notification = parseInt(getState().getIn(['notifications', 'items', 0, 'id']));
const last_read = getState().getIn(['notifications', 'lastRead']);
if (!me) return
const topNotification = parseInt(getState().getIn(['notifications', 'items', 0, 'id']))
const lastReadId = getState().getIn(['notifications', 'lastReadId'])
if (top_notification && top_notification > last_read) {
api(getState).post('/api/v1/notifications/mark_read', { id: top_notification }).then(response => {
if (topNotification && topNotification > lastReadId && lastReadId !== -1) {
api(getState).post('/api/v1/notifications/mark_read', {
id: topNotification
}).then(() => {
dispatch({
type: NOTIFICATIONS_MARK_READ,
notification: top_notification,
});
});
notification: topNotification,
})
})
}
}
};
}

View File

@ -2,13 +2,19 @@ import { Fragment } from 'react'
import { NavLink } from 'react-router-dom'
import { injectIntl, defineMessages } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { HotKeys } from 'react-hotkeys'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { me } from '../initial_state'
import {
CX,
BREAKPOINT_EXTRA_SMALL,
} from '../constants'
import Responsive from '../features/ui/util/responsive_component'
import StatusContainer from '../containers/status_container'
import Avatar from './avatar'
import Icon from './icon'
import Text from './text'
import DotTextSeperator from './dot_text_seperator'
import RelativeTimestamp from './relative_timestamp'
import DisplayName from './display_name'
const messages = defineMessages({
@ -24,15 +30,6 @@ const messages = defineMessages({
repostedStatusMultiple: { id: 'reposted_status_multiple', defaultMessage: 'and {count} others reposted your status' },
})
// : todo :
const notificationForScreenReader = (intl, message, timestamp) => {
const output = [message]
output.push(intl.formatDate(timestamp, { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }))
return output.join(', ')
}
export default
@injectIntl
class Notification extends ImmutablePureComponent {
@ -48,6 +45,7 @@ class Notification extends ImmutablePureComponent {
statusId: PropTypes.string,
type: PropTypes.string.isRequired,
isHidden: PropTypes.bool,
isUnread: PropTypes.bool,
}
render() {
@ -58,12 +56,14 @@ class Notification extends ImmutablePureComponent {
type,
statusId,
isHidden,
isUnread,
} = this.props
const count = !!accounts ? accounts.size : 0
let message
let icon
switch (type) {
case 'follow':
icon = 'group'
@ -114,29 +114,43 @@ class Notification extends ImmutablePureComponent {
)
}
const containerClasses = CX({
default: 1,
px10: 1,
cursorPointer: 1,
bgSubtle_onHover: !isUnread,
highlightedComment: isUnread,
})
return (
<div className={[_s.default, _s.px10, _s.cursorPointer, _s.bgSubtle_onHover].join(' ')}>
<div
className={containerClasses}
tabIndex='0'
aria-label={`${message} ${createdAt}`}
>
<div className={[_s.default, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.my10, _s.py10, _s.px10].join(' ')}>
<Icon id={icon} size='20px' className={[_s.fillPrimary, _s.mt5].join(' ')} />
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
<Icon id={icon} size='20px' className={[_s.fillPrimary, _s.minWidth20PX, _s.mt5, _s.mr15].join(' ')} />
</Responsive>
<div className={[_s.default, _s.ml15, _s.flexNormal].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}>
<div className={[_s.default, _s.flexNormal].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.flexWrap].join(' ')}>
{
accounts && accounts.slice(0, 8).map((account, i) => (
accounts && accounts.map((account, i) => (
<NavLink
to={`/${account.get('acct')}`}
key={`fav-avatar-${i}`}
className={_s.mr5}
className={[_s.mr5, _s.mb5].join(' ')}
>
<Avatar size={30} account={account} />
<Avatar size={34} account={account} />
</NavLink>
))
}
</div>
<div className={[_s.default, _s.pt10].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}>
<div className={[_s.default, _s.pt5].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.alignItemsEnd].join(' ')}>
<div className={_s.text}>
{
accounts && accounts.slice(0, 1).map((account, i) => (
@ -148,6 +162,15 @@ class Notification extends ImmutablePureComponent {
{' '}
{message}
</Text>
{
!!createdAt &&
<Fragment>
<DotTextSeperator />
<Text size='small' color='tertiary' className={_s.ml5}>
<RelativeTimestamp timestamp={createdAt} />
</Text>
</Fragment>
}
</div>
</div>
{

View File

@ -1,17 +1,4 @@
import { List as ImmutableList } from 'immutable'
import { openModal } from '../actions/modal'
import { mentionCompose } from '../actions/compose'
import {
reblog,
favorite,
unreblog,
unfavorite,
} from '../actions/interactions'
import {
hideStatus,
revealStatus,
} from '../actions/statuses'
import { boostModal } from '../initial_state'
import { makeGetNotification } from '../selectors'
import Notification from '../components/notification'
@ -27,25 +14,29 @@ const makeMapStateToProps = () => {
const isLikes = !!props.notification.get('like')
const isReposts = !!props.notification.get('repost')
const isGrouped = isFollows || isLikes || isReposts
const lastReadId = state.getIn(['notifications', 'lastReadId'])
if (isFollows) {
let lastUpdated
const list = props.notification.get('follow')
let accounts = ImmutableList()
list.forEach((item) => {
const account = getAccountFromState(state, item.get('account'))
accounts = accounts.set(accounts.size, account)
if (!lastUpdated) lastUpdated = item.get('created_at')
})
return {
type: 'follow',
accounts: accounts,
createdAt: undefined,
accounts: accounts,
createdAt: lastUpdated,
isUnread: false,
statusId: undefined,
}
} else if (isLikes || isReposts) {
const theType = isLikes ? 'like' : 'repost'
const list = props.notification.get(theType)
let lastUpdated = list.get('lastUpdated')
let accounts = ImmutableList()
const accountIdArr = list.get('accounts')
@ -59,7 +50,8 @@ const makeMapStateToProps = () => {
return {
type: theType,
accounts: accounts,
createdAt: undefined,
createdAt: lastUpdated,
isUnread: false,
statusId: list.get('status'),
}
} else if (!isGrouped) {
@ -68,9 +60,10 @@ const makeMapStateToProps = () => {
const statusId = notification.get('status')
return {
accounts: !!account ? ImmutableList([account]) : ImmutableList(),
type: notification.get('type'),
accounts: !!account ? ImmutableList([account]) : ImmutableList(),
createdAt: notification.get('created_at'),
isUnread: lastReadId < notification.get('id'),
statusId: statusId || undefined,
}
}
@ -79,42 +72,4 @@ const makeMapStateToProps = () => {
return mapStateToProps
}
const mapDispatchToProps = (dispatch) => ({
onMention: (account, router) => {
dispatch(mentionCompose(account, router))
},
onModalRepost (status) {
dispatch(repost(status))
},
onRepost (status, e) {
if (status.get('reblogged')) {
dispatch(unrepost(status))
} else {
if (e.shiftKey || !boostModal) {
this.onModalRepost(status)
} else {
dispatch(openModal('BOOST', { status, onRepost: this.onModalRepost }))
}
}
},
onFavorite (status) {
if (status.get('favourited')) {
dispatch(unfavorite(status))
} else {
dispatch(favorite(status))
}
},
onToggleHidden (status) {
if (status.get('hidden')) {
dispatch(revealStatus(status.get('id')))
} else {
dispatch(hideStatus(status.get('id')))
}
},
})
export default connect(makeMapStateToProps, mapDispatchToProps)(Notification)
export default connect(makeMapStateToProps)(Notification)

View File

@ -1,8 +1,7 @@
import { Fragment } from 'react'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import { createSelector } from 'reselect'
import { List as ImmutableList } from 'immutable'
import { FormattedMessage } from 'react-intl'
import debounce from 'lodash.debounce'
import {
expandNotifications,
@ -10,37 +9,33 @@ import {
dequeueNotifications,
} from '../actions/notifications'
import NotificationContainer from '../containers/notification_container'
// import ColumnSettingsContainer from './containers/column_settings_container'
import ScrollableList from '../components/scrollable_list'
import LoadMore from '../components/load_more'
import TimelineQueueButtonHeader from '../components/timeline_queue_button_header'
import Block from '../components/block'
import TimelineQueueButtonHeader from '../components/timeline_queue_button_header'
import Block from '../components/block'
const mapStateToProps = (state) => ({
notifications: state.getIn(['notifications', 'items']),
sortedNotifications: state.getIn(['notifications', 'sortedItems']),
isLoading: state.getIn(['notifications', 'isLoading'], true),
isUnread: state.getIn(['notifications', 'unread']) > 0,
hasMore: state.getIn(['notifications', 'hasMore']),
totalQueuedNotificationsCount: state.getIn(['notifications', 'totalQueuedNotificationsCount'], 0),
})
export default
@connect(mapStateToProps)
@injectIntl
class Notifications extends ImmutablePureComponent {
static propTypes = {
sortedNotifications: ImmutablePropTypes.list.isRequired,
notifications: ImmutablePropTypes.list.isRequired,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
isLoading: PropTypes.bool,
isUnread: PropTypes.bool,
hasMore: PropTypes.bool,
dequeueNotifications: PropTypes.func,
totalQueuedNotificationsCount: PropTypes.number,
}
componentWillUnmount () {
componentWillUnmount() {
this.handleLoadOlder.cancel()
this.handleScrollToTop.cancel()
this.handleScroll.cancel()
@ -52,14 +47,9 @@ class Notifications extends ImmutablePureComponent {
this.props.dispatch(scrollTopNotifications(true))
}
handleLoadGap = (maxId) => {
// maxId={index > 0 ? notifications.getIn([index - 1, 'id']) : null}
// this.props.dispatch(expandNotifications({ maxId }))
}
handleLoadOlder = debounce(() => {
const last = this.props.notifications.last()
// this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }))
this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }))
}, 300, { leading: true })
handleScrollToTop = debounce(() => {
@ -70,97 +60,57 @@ class Notifications extends ImmutablePureComponent {
this.props.dispatch(scrollTopNotifications(false))
}, 100)
setColumnRef = c => {
this.column = c
}
handleMoveUp = id => {
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1
this._selectChild(elementIndex, true)
}
handleMoveDown = id => {
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1
this._selectChild(elementIndex, false)
}
_selectChild (index, align_top) {
const container = this.column.node
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`)
if (element) {
if (align_top && container.scrollTop > element.offsetTop) {
element.scrollIntoView(true)
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
element.scrollIntoView(false)
}
element.focus()
}
}
handleDequeueNotifications = () => {
this.props.dispatch(dequeueNotifications())
}
render () {
render() {
const {
intl,
notifications,
sortedNotifications,
isLoading,
isUnread,
hasMore,
totalQueuedNotificationsCount
totalQueuedNotificationsCount,
} = this.props
let scrollableContent = null
// : todo : include follow requests
// console.log('--0--notifications:', hasMore)
if (isLoading && this.scrollableContent) {
scrollableContent = this.scrollableContent
} else if (notifications.size > 0 || hasMore) {
scrollableContent = notifications.map((item, index) => item === null ? (
<LoadMore disabled={isLoading} onClick={this.handleLoadGap} />
) : (
} else if (sortedNotifications.size > 0 || hasMore) {
scrollableContent = sortedNotifications.map((item, index) => (
<NotificationContainer
key={`notification-${index}`}
notification={item}
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
/>
))
} else {
scrollableContent = null
}
this.scrollableContent = scrollableContent
return (
<div ref={this.setColumnRef}>
<Fragment>
<TimelineQueueButtonHeader
onClick={this.handleDequeueNotifications}
count={totalQueuedNotificationsCount}
itemType='notification'
/>
<Block>
<ScrollableList
scrollKey='notifications'
isLoading={isLoading}
showLoading={isLoading && notifications.size === 0}
showLoading={isLoading && sortedNotifications.size === 0}
hasMore={hasMore}
emptyMessage={<FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />}
onLoadMore={this.handleLoadOlder}
onScrollToTop={this.handleScrollToTop}
onScroll={this.handleScroll}
>
{ scrollableContent }
{scrollableContent}
</ScrollableList>
</Block>
</div>
</Fragment>
)
}

View File

@ -24,6 +24,7 @@ export const isStaff = getMeta('is_staff');
export const forceSingleColumn = !getMeta('advanced_layout');
export const promotions = initialState && initialState.promotions;
export const unreadCount = getMeta('unread_count');
export const lastReadNotificationId = getMeta('last_read_notification_id');
export const monthlyExpensesComplete = getMeta('monthly_expenses_complete');
export const favouritesCount = getMeta('favourites_count');
export const compactMode = false;

View File

@ -11,25 +11,29 @@ import {
NOTIFICATIONS_DEQUEUE,
MAX_QUEUED_NOTIFICATIONS,
NOTIFICATIONS_MARK_READ,
} from '../actions/notifications';
} from '../actions/notifications'
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
} from '../actions/accounts';
import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from '../actions/timelines';
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
} from '../actions/accounts'
import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from '../actions/timelines'
import { Range, Map as ImmutableMap, List as ImmutableList } from 'immutable'
import { unreadCount, lastReadNotificationId } from '../initial_state'
import compareId from '../utils/compare_id';
import { unreadCount } from '../initial_state';
const DEFAULT_NOTIFICATIONS_LIMIT = 20
const initialState = ImmutableMap({
items: ImmutableList(),
sortedItems: ImmutableList(),
lastId: null,
hasMore: true,
top: false,
unread: 0,
isLoading: false,
queuedNotifications: ImmutableList(), //max = MAX_QUEUED_NOTIFICATIONS
totalQueuedNotificationsCount: 0, //used for queuedItems overflow for MAX_QUEUED_NOTIFICATIONS+
lastRead: -1,
lastReadId: -1,
filter: ImmutableMap({
active: 'all',
onlyVerified: false,
@ -37,7 +41,7 @@ const initialState = ImmutableMap({
}),
});
const notificationToMap = notification => ImmutableMap({
const notificationToMap = (notification) => ImmutableMap({
id: notification.id,
type: notification.type,
account: notification.account.id,
@ -45,98 +49,137 @@ const notificationToMap = notification => ImmutableMap({
status: notification.status ? notification.status.id : null,
});
const normalizeNotification = (state, notification) => {
const top = state.get('top');
const makeSortedNotifications = (state) => {
let finalSortedItems = ImmutableList()
const items = state.get('items')
if (!top) {
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(notification));
});
};
const expandNormalizedNotifications = (state, notifications, next) => {
//Grouped items
let follows = ImmutableList()
let likes = {}
let reposts = {}
const chunks = Range(0, items.count(), DEFAULT_NOTIFICATIONS_LIMIT)
.map((chunkStart) => items.slice(chunkStart, chunkStart + DEFAULT_NOTIFICATIONS_LIMIT))
let items = ImmutableList()
chunks.forEach((chunk) => {
let sortedItems = ImmutableList()
notifications.forEach((n) => {
const notification = notificationToMap(n)
const statusId = notification.get('status')
const type = notification.get('type')
let follows = ImmutableList()
let likes = {}
let reposts = {}
let followIndex = -1
let indexesForStatusesForReposts = {}
let indexesForStatusesForFavorites = {}
switch (type) {
case 'follow': {
follows = follows.set(follows.size, notification)
break
chunk.forEach((notification) => {
const statusId = notification.get('status')
const type = notification.get('type')
switch (type) {
case 'follow': {
if (followIndex === -1) followIndex = sortedItems.size
sortedItems = sortedItems.set(followIndex, ImmutableMap())
follows = follows.set(follows.size, notification)
break
}
case 'favourite': {
if (likes[statusId] === undefined) {
let size = sortedItems.size
sortedItems = sortedItems.set(size, ImmutableMap())
indexesForStatusesForFavorites[statusId] = size
likes[statusId] = []
}
likes[statusId].push({
account: notification.get('account'),
created_at: notification.get('created_at'),
})
break
}
case 'reblog': {
if (reposts[statusId] === undefined) {
let size = sortedItems.size
sortedItems = sortedItems.set(size, ImmutableMap())
indexesForStatusesForReposts[statusId] = size
reposts[statusId] = []
}
reposts[statusId].push({
account: notification.get('account'),
created_at: notification.get('created_at'),
})
break
}
default: {
sortedItems = sortedItems.set(sortedItems.size, notification)
break
}
}
case 'favourite': {
if (likes[statusId] === undefined) likes[statusId] = []
likes[statusId].push({
account: notification.get('account'),
})
break
if (follows.size > 0) {
sortedItems = sortedItems.set(followIndex, ImmutableMap({
follow: follows,
}))
}
case 'reblog': {
if (reposts[statusId] === undefined) reposts[statusId] = []
reposts[statusId].push({
account: notification.get('account'),
})
break
if (Object.keys(likes).length > 0) {
for (const statusId in likes) {
if (likes.hasOwnProperty(statusId)) {
const likeArr = likes[statusId]
const accounts = likeArr.map((l) => l.account)
const lastUpdated = likeArr[0]['created_at']
sortedItems = sortedItems.set(indexesForStatusesForFavorites[statusId], ImmutableMap({
like: ImmutableMap({
accounts,
lastUpdated,
status: statusId,
})
}))
}
}
}
default: {
items = items.set(items.size, notification)
break
if (Object.keys(reposts).length > 0) {
for (const statusId in reposts) {
if (reposts.hasOwnProperty(statusId)) {
const repostArr = reposts[statusId]
const accounts = repostArr.map((l) => l.account)
const lastUpdated = repostArr[0]['created_at']
sortedItems = sortedItems.set(indexesForStatusesForReposts[statusId], ImmutableMap({
repost: ImmutableMap({
accounts,
lastUpdated,
status: statusId,
})
}))
}
}
}
}
})
finalSortedItems = finalSortedItems.concat(sortedItems)
})
if (follows.size > 0) {
items = items.set(items.size, ImmutableMap({
follow: follows,
}))
}
if (Object.keys(likes).length > 0) {
for (const statusId in likes) {
if (likes.hasOwnProperty(statusId)) {
const likeArr = likes[statusId]
const accounts = likeArr.map((l) => l.account)
items = items.set(items.size, ImmutableMap({
like: ImmutableMap({
status: statusId,
accounts: accounts,
})
}))
}
}
}
if (Object.keys(reposts).length > 0) {
for (const statusId in reposts) {
if (reposts.hasOwnProperty(statusId)) {
const repostArr = reposts[statusId]
const accounts = repostArr.map((l) => l.account)
items = items.set(items.size, ImmutableMap({
repost: ImmutableMap({
status: statusId,
accounts: accounts,
})
}))
}
}
return state.set('sortedItems', finalSortedItems)
}
const normalizeNotification = (state, notification) => {
const top = state.get('top')
if (!top) {
state = state.update('unread', (unread) => unread + 1)
}
return state.withMutations(mutable => {
state = state.update('items', (list) => {
if (top && list.size > 40) list = list.take(20)
return list.unshift(notificationToMap(notification))
})
return makeSortedNotifications(state)
}
const expandNormalizedNotifications = (state, notifications, next) => {
let items = ImmutableList()
notifications.forEach((n, i) => {
items = items.set(i, notificationToMap(n))
})
state = state.withMutations((mutable) => {
if (!items.isEmpty()) {
mutable.update('items', list => {
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'))
)
@ -145,35 +188,35 @@ const expandNormalizedNotifications = (state, notifications, next) => {
item => item !== null && compareId(item.get('id'), items.first().get('id')) > 0
)
const pop = list.take(firstIndex).concat(items, list.skip(lastIndex))
return pop
return list.take(firstIndex).concat(items, list.skip(lastIndex))
})
}
if (!next) {
mutable.set('hasMore', false);
}
if (!next) mutable.set('hasMore', false)
mutable.set('isLoading', false)
})
mutable.set('isLoading', false);
});
};
const filterNotifications = (state, relationship) => {
return state.update('items', list => list.filterNot(item => item !== null && item.get('account') === relationship.id));
};
return makeSortedNotifications(state)
}
const updateTop = (state, top) => {
if (top) {
state = state.set('unread', 0);
}
return state.withMutations((mutable) => {
if (top) mutable.set('unread', 0)
mutable.set('top', top)
})
}
return state.set('top', top);
};
const filterNotifications = (state, relationship) => {
const filterer = (list) => list.filterNot((item) => !!item && item.get('account') === relationship.id)
state = state.update('items', filterer)
return makeSortedNotifications(state)
}
const deleteByStatus = (state, statusId) => {
return state.update('items', list => list.filterNot(item => item !== null && item.get('status') === statusId));
};
const filterer = (list) => list.filterNot((item) => !!item && item.get('status') === statusId)
state = state.update('items', filterer)
return makeSortedNotifications(state)
}
const updateNotificationsQueue = (state, notification, intlMessages, intlLocale) => {
const queuedNotifications = state.getIn(['queuedNotifications'], ImmutableList());
@ -181,9 +224,10 @@ const updateNotificationsQueue = (state, notification, intlMessages, intlLocale)
const totalQueuedNotificationsCount = state.getIn(['totalQueuedNotificationsCount'], 0);
const unread = state.getIn(['unread'], 0)
let alreadyExists = queuedNotifications.find(existingQueuedNotification => existingQueuedNotification.id === notification.id);
if (!alreadyExists) alreadyExists = listedNotifications.find(existingListedNotification => existingListedNotification.get('id') === notification.id);
let alreadyExists = queuedNotifications.find((existingQueuedNotification) => existingQueuedNotification.id === notification.id);
if (!alreadyExists) {
alreadyExists = listedNotifications.find((existingListedNotification) => existingListedNotification.get('id') === notification.id);
}
if (alreadyExists) {
return state;
}
@ -206,9 +250,12 @@ const updateNotificationsQueue = (state, notification, intlMessages, intlLocale)
export default function notifications(state = initialState, action) {
switch(action.type) {
case NOTIFICATIONS_INITIALIZE:
return state.set('unread', unreadCount);
return state.withMutations((mutable) => {
mutable.set('unread', unreadCount)
mutable.set('lastReadId', lastReadNotificationId)
})
case NOTIFICATIONS_MARK_READ:
return state.set('lastRead', action.notification);
return state.set('lastReadId', action.lastReadId);
case NOTIFICATIONS_EXPAND_REQUEST:
return state.set('isLoading', true);
case NOTIFICATIONS_EXPAND_FAIL:

View File

@ -497,6 +497,7 @@ body {
.maxWidth100PC42PX { max-width: calc(100% - 42px); }
.minWidth330PX { min-width: 330px; }
.minWidth20PX { min-width: 20px; }
.minWidth14PX { min-width: 14px; }
.width100PC { width: 100%; }

View File

@ -37,6 +37,7 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:group_in_home_feed] = object.current_account.user.setting_group_in_home_feed
store[:is_staff] = object.current_account.user.staff?
store[:unread_count] = unread_count object.current_account
store[:last_read_notification_id] = object.current_account.user.last_read_notification
store[:monthly_expenses_complete] = Redis.current.get("monthly_funding_amount") || 0
store[:favourites_count] = object.current_account.favourites.count.to_s
end