@ -9,6 +9,7 @@ import {
NOTIFICATIONS _LOAD _PENDING ,
NOTIFICATIONS _LOAD _PENDING ,
NOTIFICATIONS _MOUNT ,
NOTIFICATIONS _MOUNT ,
NOTIFICATIONS _UNMOUNT ,
NOTIFICATIONS _UNMOUNT ,
NOTIFICATIONS _MARK _AS _READ ,
} from '../actions/notifications' ;
} from '../actions/notifications' ;
import {
import {
ACCOUNT _BLOCK _SUCCESS ,
ACCOUNT _BLOCK _SUCCESS ,
@ -16,6 +17,13 @@ import {
FOLLOW _REQUEST _AUTHORIZE _SUCCESS ,
FOLLOW _REQUEST _AUTHORIZE _SUCCESS ,
FOLLOW _REQUEST _REJECT _SUCCESS ,
FOLLOW _REQUEST _REJECT _SUCCESS ,
} from '../actions/accounts' ;
} from '../actions/accounts' ;
import {
MARKERS _FETCH _SUCCESS ,
} from '../actions/markers' ;
import {
APP _FOCUS ,
APP _UNFOCUS ,
} from '../actions/app' ;
import { DOMAIN _BLOCK _SUCCESS } from 'mastodon/actions/domain_blocks' ;
import { DOMAIN _BLOCK _SUCCESS } from 'mastodon/actions/domain_blocks' ;
import { TIMELINE _DELETE , TIMELINE _DISCONNECT } from '../actions/timelines' ;
import { TIMELINE _DELETE , TIMELINE _DISCONNECT } from '../actions/timelines' ;
import { Map as ImmutableMap , List as ImmutableList } from 'immutable' ;
import { Map as ImmutableMap , List as ImmutableList } from 'immutable' ;
@ -26,8 +34,11 @@ const initialState = ImmutableMap({
items : ImmutableList ( ) ,
items : ImmutableList ( ) ,
hasMore : true ,
hasMore : true ,
top : false ,
top : false ,
mounted : false ,
mounted : 0 ,
unread : 0 ,
unread : 0 ,
lastReadId : '0' ,
readMarkerId : '0' ,
isTabVisible : true ,
isLoading : false ,
isLoading : false ,
} ) ;
} ) ;
@ -46,8 +57,10 @@ const normalizeNotification = (state, notification, usePendingItems) => {
return state . update ( 'pendingItems' , list => list . unshift ( notificationToMap ( notification ) ) ) . update ( 'unread' , unread => unread + 1 ) ;
return state . update ( 'pendingItems' , list => list . unshift ( notificationToMap ( notification ) ) ) . update ( 'unread' , unread => unread + 1 ) ;
}
}
if ( ! top ) {
if ( shouldCountUnreadNotifications ( state ) ) {
state = state . update ( 'unread' , unread => unread + 1 ) ;
state = state . update ( 'unread' , unread => unread + 1 ) ;
} else {
state = state . set ( 'lastReadId' , notification . id ) ;
}
}
return state . update ( 'items' , list => {
return state . update ( 'items' , list => {
@ -60,6 +73,7 @@ const normalizeNotification = (state, notification, usePendingItems) => {
} ;
} ;
const expandNormalizedNotifications = ( state , notifications , next , isLoadingRecent , usePendingItems ) => {
const expandNormalizedNotifications = ( state , notifications , next , isLoadingRecent , usePendingItems ) => {
const lastReadId = state . get ( 'lastReadId' ) ;
let items = ImmutableList ( ) ;
let items = ImmutableList ( ) ;
notifications . forEach ( ( n , i ) => {
notifications . forEach ( ( n , i ) => {
@ -87,6 +101,15 @@ const expandNormalizedNotifications = (state, notifications, next, isLoadingRece
mutable . set ( 'hasMore' , false ) ;
mutable . set ( 'hasMore' , false ) ;
}
}
if ( shouldCountUnreadNotifications ( state ) ) {
mutable . update ( 'unread' , unread => unread + items . count ( item => compareId ( item . get ( 'id' ) , lastReadId ) > 0 ) ) ;
} else {
const mostRecent = items . find ( item => item !== null ) ;
if ( mostRecent && compareId ( lastReadId , mostRecent . get ( 'id' ) ) < 0 ) {
mutable . set ( 'lastReadId' , mostRecent . get ( 'id' ) ) ;
}
}
mutable . set ( 'isLoading' , false ) ;
mutable . set ( 'isLoading' , false ) ;
} ) ;
} ) ;
} ;
} ;
@ -96,21 +119,92 @@ const filterNotifications = (state, accountIds, type) => {
return state . update ( 'items' , helper ) . update ( 'pendingItems' , helper ) ;
return state . update ( 'items' , helper ) . update ( 'pendingItems' , helper ) ;
} ;
} ;
const clearUnread = ( state ) => {
state = state . set ( 'unread' , state . get ( 'pendingItems' ) . size ) ;
const lastNotification = state . get ( 'items' ) . find ( item => item !== null ) ;
return state . set ( 'lastReadId' , lastNotification ? lastNotification . get ( 'id' ) : '0' ) ;
} ;
const updateTop = ( state , top ) => {
const updateTop = ( state , top ) => {
if ( top ) {
state = state . set ( 'top' , top ) ;
state = state . set ( 'unread' , state . get ( 'pendingItems' ) . size ) ;
if ( ! shouldCountUnreadNotifications ( state ) ) {
state = clearUnread ( state ) ;
}
}
return state . set ( 'top' , top ) ;
return state ;
} ;
} ;
const deleteByStatus = ( state , statusId ) => {
const deleteByStatus = ( state , statusId ) => {
const lastReadId = state . get ( 'lastReadId' ) ;
if ( shouldCountUnreadNotifications ( state ) ) {
const deletedUnread = state . get ( 'items' ) . filter ( item => item !== null && item . get ( 'status' ) === statusId && compareId ( item . get ( 'id' ) , lastReadId ) > 0 ) ;
state = state . update ( 'unread' , unread => unread - deletedUnread . size ) ;
}
const helper = list => list . filterNot ( item => item !== null && item . get ( 'status' ) === statusId ) ;
const helper = list => list . filterNot ( item => item !== null && item . get ( 'status' ) === statusId ) ;
const deletedUnread = state . get ( 'pendingItems' ) . filter ( item => item !== null && item . get ( 'status' ) === statusId && compareId ( item . get ( 'id' ) , lastReadId ) > 0 ) ;
state = state . update ( 'unread' , unread => unread - deletedUnread . size ) ;
return state . update ( 'items' , helper ) . update ( 'pendingItems' , helper ) ;
return state . update ( 'items' , helper ) . update ( 'pendingItems' , helper ) ;
} ;
} ;
const updateMounted = ( state ) => {
state = state . update ( 'mounted' , count => count + 1 ) ;
if ( ! shouldCountUnreadNotifications ( state ) ) {
state = state . set ( 'readMarkerId' , state . get ( 'lastReadId' ) ) ;
state = clearUnread ( state ) ;
}
return state ;
} ;
const updateVisibility = ( state , visibility ) => {
state = state . set ( 'isTabVisible' , visibility ) ;
if ( ! shouldCountUnreadNotifications ( state ) ) {
state = state . set ( 'readMarkerId' , state . get ( 'lastReadId' ) ) ;
state = clearUnread ( state ) ;
}
return state ;
} ;
const shouldCountUnreadNotifications = ( state ) => {
const isTabVisible = state . get ( 'isTabVisible' ) ;
const isOnTop = state . get ( 'top' ) ;
const isMounted = state . get ( 'mounted' ) > 0 ;
const lastReadId = state . get ( 'lastReadId' ) ;
const lastItemReached = ! state . get ( 'hasMore' ) || lastReadId === '0' || ( ! state . get ( 'items' ) . isEmpty ( ) && compareId ( state . get ( 'items' ) . last ( ) . get ( 'id' ) , lastReadId ) <= 0 ) ;
return ! ( isTabVisible && isOnTop && isMounted && lastItemReached ) ;
} ;
const recountUnread = ( state , last _read _id ) => {
return state . withMutations ( mutable => {
if ( compareId ( last _read _id , mutable . get ( 'lastReadId' ) ) > 0 ) {
mutable . set ( 'lastReadId' , last _read _id ) ;
}
if ( compareId ( last _read _id , mutable . get ( 'readMarkerId' ) ) > 0 ) {
mutable . set ( 'readMarkerId' , last _read _id ) ;
}
if ( state . get ( 'unread' ) > 0 || shouldCountUnreadNotifications ( state ) ) {
mutable . set ( 'unread' , mutable . get ( 'pendingItems' ) . count ( item => item !== null ) + mutable . get ( 'items' ) . count ( item => item && compareId ( item . get ( 'id' ) , last _read _id ) > 0 ) ) ;
}
} ) ;
} ;
export default function notifications ( state = initialState , action ) {
export default function notifications ( state = initialState , action ) {
switch ( action . type ) {
switch ( action . type ) {
case MARKERS _FETCH _SUCCESS :
return action . markers . notifications ? recountUnread ( state , action . markers . notifications . last _read _id ) : state ;
case NOTIFICATIONS _MOUNT :
return updateMounted ( state ) ;
case NOTIFICATIONS _UNMOUNT :
return state . update ( 'mounted' , count => count - 1 ) ;
case APP _FOCUS :
return updateVisibility ( state , true ) ;
case APP _UNFOCUS :
return updateVisibility ( state , false ) ;
case NOTIFICATIONS _LOAD _PENDING :
case NOTIFICATIONS _LOAD _PENDING :
return state . update ( 'items' , list => state . get ( 'pendingItems' ) . concat ( list . take ( 40 ) ) ) . set ( 'pendingItems' , ImmutableList ( ) ) . set ( 'unread' , 0 ) ;
return state . update ( 'items' , list => state . get ( 'pendingItems' ) . concat ( list . take ( 40 ) ) ) . set ( 'pendingItems' , ImmutableList ( ) ) . set ( 'unread' , 0 ) ;
case NOTIFICATIONS _EXPAND _REQUEST :
case NOTIFICATIONS _EXPAND _REQUEST :
@ -144,10 +238,9 @@ export default function notifications(state = initialState, action) {
return action . timeline === 'home' ?
return action . timeline === 'home' ?
state . update ( action . usePendingItems ? 'pendingItems' : 'items' , items => items . first ( ) ? items . unshift ( null ) : items ) :
state . update ( action . usePendingItems ? 'pendingItems' : 'items' , items => items . first ( ) ? items . unshift ( null ) : items ) :
state ;
state ;
case NOTIFICATIONS _MOUNT :
case NOTIFICATIONS _MARK _AS _READ :
return state . set ( 'mounted' , true ) ;
const lastNotification = state . get ( 'items' ) . find ( item => item !== null ) ;
case NOTIFICATIONS _UNMOUNT :
return lastNotification ? recountUnread ( state , lastNotification . get ( 'id' ) ) : state ;
return state . set ( 'mounted' , false ) ;
default :
default :
return state ;
return state ;
}
}