diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index ee148e4ef2f..fb84cd01e5f 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -28,6 +28,8 @@ export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; export const NOTIFICATIONS_MOUNT = 'NOTIFICATIONS_MOUNT'; export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT'; +export const NOTIFICATIONS_SET_VISIBILITY = 'NOTIFICATIONS_SET_VISIBILITY'; + defineMessages({ mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' }, }); @@ -231,3 +233,10 @@ export function unmountNotifications() { type: NOTIFICATIONS_UNMOUNT, }; }; + +export function notificationsSetVisibility(visibility) { + return { + type: NOTIFICATIONS_SET_VISIBILITY, + visibility: visibility, + }; +}; diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index 1cff94321e8..c41436090ca 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -10,7 +10,7 @@ import { isMobile } from 'flavours/glitch/util/is_mobile'; import { debounce } from 'lodash'; import { uploadCompose, resetCompose } from 'flavours/glitch/actions/compose'; import { expandHomeTimeline } from 'flavours/glitch/actions/timelines'; -import { expandNotifications } from 'flavours/glitch/actions/notifications'; +import { expandNotifications, notificationsSetVisibility } from 'flavours/glitch/actions/notifications'; import { fetchFilters } from 'flavours/glitch/actions/filters'; import { clearHeight } from 'flavours/glitch/actions/height_cache'; import { WrappedSwitch, WrappedRoute } from 'flavours/glitch/util/react_router_helpers'; @@ -206,7 +206,27 @@ export default class UI extends React.Component { } } + handleVisibilityChange = () => { + const visibility = !document[this.visibilityHiddenProp]; + this.props.dispatch(notificationsSetVisibility(visibility)); + } + componentWillMount () { + if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support + this.visibilityHiddenProp = 'hidden'; + this.visibilityChange = 'visibilitychange'; + } else if (typeof document.msHidden !== 'undefined') { + this.visibilityHiddenProp = 'msHidden'; + this.visibilityChange = 'msvisibilitychange'; + } else if (typeof document.webkitHidden !== 'undefined') { + this.visibilityHiddenProp = 'webkitHidden'; + this.visibilityChange = 'webkitvisibilitychange'; + } + if (this.visibilityChange !== undefined) { + document.addEventListener(this.visibilityChange, this.handleVisibilityChange, false); + this.handleVisibilityChange(); + } + window.addEventListener('beforeunload', this.handleBeforeUnload, false); window.addEventListener('resize', this.handleResize, { passive: true }); document.addEventListener('dragenter', this.handleDragEnter, false); @@ -250,6 +270,10 @@ export default class UI extends React.Component { } componentWillUnmount () { + if (this.visibilityChange !== undefined) { + document.removeEventListener(this.visibilityChange, this.handleVisibilityChange); + } + window.removeEventListener('beforeunload', this.handleBeforeUnload); window.removeEventListener('resize', this.handleResize); document.removeEventListener('dragenter', this.handleDragEnter); diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index 9087e226ca7..0b816e85eec 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -1,6 +1,7 @@ import { NOTIFICATIONS_MOUNT, NOTIFICATIONS_UNMOUNT, + NOTIFICATIONS_SET_VISIBILITY, NOTIFICATIONS_UPDATE, NOTIFICATIONS_EXPAND_SUCCESS, NOTIFICATIONS_EXPAND_REQUEST, @@ -31,6 +32,7 @@ const initialState = ImmutableMap({ lastReadId: '0', isLoading: false, cleaningMode: false, + isTabVisible: true, // notification removal mark of new notifs loaded whilst cleaningMode is true. markNewForDelete: false, }); @@ -44,7 +46,7 @@ const notificationToMap = (state, notification) => ImmutableMap({ }); const normalizeNotification = (state, notification) => { - const top = state.get('top') && state.get('mounted') > 0; + const top = !shouldCountUnreadNotifications(state); if (top) { state = state.set('lastReadId', notification.id); @@ -62,7 +64,7 @@ const normalizeNotification = (state, notification) => { }; const expandNormalizedNotifications = (state, notifications, next) => { - const top = state.get('top') && state.get('mounted') > 0; + const top = !(shouldCountUnreadNotifications(state)); const lastReadId = state.get('lastReadId'); let items = ImmutableList(); @@ -112,7 +114,9 @@ const clearUnread = (state) => { } const updateTop = (state, top) => { - if (top && state.get('mounted') > 0) { + state = state.set('top', top); + + if (!shouldCountUnreadNotifications(state)) { state = clearUnread(state); } @@ -120,7 +124,7 @@ const updateTop = (state, top) => { }; const deleteByStatus = (state, statusId) => { - const top = state.get('top') && state.get('mounted') > 0; + const top = !(shouldCountUnreadNotifications(state)); if (!top) { const lastReadId = state.get('lastReadId'); const deletedUnread = state.get('items').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0); @@ -157,14 +161,36 @@ const deleteMarkedNotifs = (state) => { return state.update('items', list => list.filterNot(item => item.get('markedForDelete'))); }; +const updateMounted = (state) => { + state = state.update('mounted', count => count + 1); + if (!shouldCountUnreadNotifications(state)) { + state = clearUnread(state); + } + return state; +}; + +const updateVisibility = (state, visibility) => { + state = state.set('isTabVisible', visibility); + if (!shouldCountUnreadNotifications(state)) { + state = clearUnread(state); + } + return state; +}; + +const shouldCountUnreadNotifications = (state) => { + return !(state.get('isTabVisible') && state.get('top') && state.get('mounted') > 0); +}; + export default function notifications(state = initialState, action) { let st; switch(action.type) { case NOTIFICATIONS_MOUNT: - return (state.get('top') ? clearUnread(state) : state).update('mounted', count => count + 1); + return updateMounted(state); case NOTIFICATIONS_UNMOUNT: return state.update('mounted', count => count - 1); + case NOTIFICATIONS_SET_VISIBILITY: + return updateVisibility(state, action.visibility); case NOTIFICATIONS_EXPAND_REQUEST: case NOTIFICATIONS_DELETE_MARKED_REQUEST: return state.set('isLoading', true);