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:
parent
83696f8098
commit
663f46b166
@ -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,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -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>
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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%; }
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user