mirror of https://github.com/mastodon/mastodon.git
Handle unread notifications
parent
a4ba310adf
commit
c14e537ef0
|
@ -19,6 +19,7 @@ import { Icon } from 'mastodon/components/icon';
|
||||||
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
|
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
|
||||||
import { useIdentity } from 'mastodon/identity_context';
|
import { useIdentity } from 'mastodon/identity_context';
|
||||||
import type { NotificationGap } from 'mastodon/reducers/notifications_groups';
|
import type { NotificationGap } from 'mastodon/reducers/notifications_groups';
|
||||||
|
import { selectUnreadNotificationsGroupsCount } from 'mastodon/selectors/notifications';
|
||||||
import {
|
import {
|
||||||
selectNeedsNotificationPermission,
|
selectNeedsNotificationPermission,
|
||||||
selectSettingsNotificationsExcludedTypes,
|
selectSettingsNotificationsExcludedTypes,
|
||||||
|
@ -35,8 +36,8 @@ import {
|
||||||
expandNotifications,
|
expandNotifications,
|
||||||
scrollTopNotifications,
|
scrollTopNotifications,
|
||||||
loadPending,
|
loadPending,
|
||||||
mountNotifications,
|
// mountNotifications,
|
||||||
unmountNotifications,
|
// unmountNotifications,
|
||||||
markNotificationsAsRead,
|
markNotificationsAsRead,
|
||||||
} from '../../actions/notifications';
|
} from '../../actions/notifications';
|
||||||
import Column from '../../components/column';
|
import Column from '../../components/column';
|
||||||
|
@ -81,52 +82,35 @@ const getNotifications = createSelector(
|
||||||
);
|
);
|
||||||
|
|
||||||
// const mapStateToProps = (state) => ({
|
// const mapStateToProps = (state) => ({
|
||||||
// isUnread:
|
|
||||||
// state.getIn(['notifications', 'unread']) > 0 ||
|
|
||||||
// state.getIn(['notifications', 'pendingItems']).size > 0,
|
|
||||||
// numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList())
|
// numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList())
|
||||||
// .size,
|
// .size,
|
||||||
// canMarkAsRead:
|
|
||||||
// state.getIn(['settings', 'notifications', 'showUnread']) &&
|
|
||||||
// state.getIn(['notifications', 'readMarkerId']) !== '0' &&
|
|
||||||
// getNotifications(state).some(
|
|
||||||
// (item) =>
|
|
||||||
// item !== null &&
|
|
||||||
// compareId(
|
|
||||||
// item.get('id'),
|
|
||||||
// state.getIn(['notifications', 'readMarkerId']),
|
|
||||||
// ) > 0,
|
|
||||||
// ),
|
|
||||||
// });
|
// });
|
||||||
|
|
||||||
export const Notifications: React.FC<{
|
export const Notifications: React.FC<{
|
||||||
columnId?: string;
|
columnId?: string;
|
||||||
isUnread?: boolean;
|
|
||||||
multiColumn?: boolean;
|
multiColumn?: boolean;
|
||||||
numPending: number;
|
numPending: number;
|
||||||
}> = ({ isUnread, columnId, multiColumn, numPending }) => {
|
}> = ({ columnId, multiColumn, numPending }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const notifications = useAppSelector(getNotifications);
|
const notifications = useAppSelector(getNotifications);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isLoading = useAppSelector((s) => s.notificationsGroups.isLoading);
|
const isLoading = useAppSelector((s) => s.notificationsGroups.isLoading);
|
||||||
const hasMore = useAppSelector((s) => s.notificationsGroups.hasMore);
|
const hasMore = useAppSelector((s) => s.notificationsGroups.hasMore);
|
||||||
const readMarkerId = useAppSelector(
|
|
||||||
(s) => s.notificationsGroups.readMarkerId,
|
|
||||||
);
|
|
||||||
const lastReadId = useAppSelector((s) =>
|
const lastReadId = useAppSelector((s) =>
|
||||||
selectSettingsNotificationsShowUnread(s)
|
selectSettingsNotificationsShowUnread(s) ? s.markers.notifications : '0',
|
||||||
? s.notificationsGroups.readMarkerId
|
|
||||||
: '0',
|
|
||||||
);
|
);
|
||||||
const canMarkAsRead = useAppSelector(
|
|
||||||
(s) =>
|
const unreadNotificationsCount = useAppSelector(
|
||||||
selectSettingsNotificationsShowUnread(s) &&
|
selectUnreadNotificationsGroupsCount,
|
||||||
s.notificationsGroups.readMarkerId !== '0' &&
|
|
||||||
notifications.some(
|
|
||||||
(item) =>
|
|
||||||
item.type !== 'gap' && compareId(item.group_key, readMarkerId) > 0,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isUnread = unreadNotificationsCount > 0;
|
||||||
|
|
||||||
|
const canMarkAsRead =
|
||||||
|
useAppSelector(selectSettingsNotificationsShowUnread) &&
|
||||||
|
unreadNotificationsCount > 0;
|
||||||
|
|
||||||
const needsNotificationPermission = useAppSelector(
|
const needsNotificationPermission = useAppSelector(
|
||||||
selectNeedsNotificationPermission,
|
selectNeedsNotificationPermission,
|
||||||
);
|
);
|
||||||
|
@ -157,14 +141,14 @@ export const Notifications: React.FC<{
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(mountNotifications());
|
// dispatch(mountNotifications());
|
||||||
|
|
||||||
// FIXME: remove once this becomes the main implementation
|
// FIXME: remove once this becomes the main implementation
|
||||||
void dispatch(fetchNotifications());
|
void dispatch(fetchNotifications());
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
dispatch(unmountNotifications());
|
// dispatch(unmountNotifications());
|
||||||
dispatch(scrollTopNotifications(false));
|
// dispatch(scrollTopNotifications(false));
|
||||||
};
|
};
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
@ -282,7 +266,9 @@ export const Notifications: React.FC<{
|
||||||
onMoveUp={handleMoveUp}
|
onMoveUp={handleMoveUp}
|
||||||
onMoveDown={handleMoveDown}
|
onMoveDown={handleMoveDown}
|
||||||
unread={
|
unread={
|
||||||
lastReadId !== '0' && compareId(item.group_key, lastReadId) > 0
|
lastReadId !== '0' &&
|
||||||
|
!!item.page_max_id &&
|
||||||
|
compareId(item.page_max_id, lastReadId) > 0
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { NavigationPortal } from 'mastodon/components/navigation_portal';
|
||||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||||
import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
|
import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
|
||||||
import { transientSingleColumn } from 'mastodon/is_mobile';
|
import { transientSingleColumn } from 'mastodon/is_mobile';
|
||||||
|
import { selectUnreadNotificationsGroupsCount } from 'mastodon/selectors/notifications';
|
||||||
|
|
||||||
import ColumnLink from './column_link';
|
import ColumnLink from './column_link';
|
||||||
import DisabledAccountBanner from './disabled_account_banner';
|
import DisabledAccountBanner from './disabled_account_banner';
|
||||||
|
@ -62,14 +63,27 @@ const NotificationsLink = () => {
|
||||||
const count = useSelector(state => state.getIn(['notifications', 'unread']));
|
const count = useSelector(state => state.getIn(['notifications', 'unread']));
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const newCount = useSelector(selectUnreadNotificationsGroupsCount);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<ColumnLink
|
<ColumnLink
|
||||||
|
key='notifications'
|
||||||
transparent
|
transparent
|
||||||
to='/notifications'
|
to='/notifications'
|
||||||
icon={<IconWithBadge id='bell' icon={NotificationsIcon} count={count} className='column-link__icon' />}
|
icon={<IconWithBadge id='bell' icon={NotificationsIcon} count={count} className='column-link__icon' />}
|
||||||
activeIcon={<IconWithBadge id='bell' icon={NotificationsActiveIcon} count={count} className='column-link__icon' />}
|
activeIcon={<IconWithBadge id='bell' icon={NotificationsActiveIcon} count={count} className='column-link__icon' />}
|
||||||
text={intl.formatMessage(messages.notifications)}
|
text={intl.formatMessage(messages.notifications)}
|
||||||
/>
|
/>
|
||||||
|
<ColumnLink
|
||||||
|
key='notifications-v2'
|
||||||
|
transparent
|
||||||
|
to='/notifications_v2'
|
||||||
|
icon={<IconWithBadge id='bell' icon={NotificationsIcon} count={newCount} className='column-link__icon' />}
|
||||||
|
activeIcon={<IconWithBadge id='bell' icon={NotificationsActiveIcon} count={newCount} className='column-link__icon' />}
|
||||||
|
text={"New Notifications"}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -31,18 +31,14 @@ export interface NotificationGap {
|
||||||
|
|
||||||
interface NotificationGroupsState {
|
interface NotificationGroupsState {
|
||||||
groups: (NotificationGroup | NotificationGap)[];
|
groups: (NotificationGroup | NotificationGap)[];
|
||||||
unread: number;
|
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
hasMore: boolean;
|
hasMore: boolean;
|
||||||
readMarkerId: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: NotificationGroupsState = {
|
const initialState: NotificationGroupsState = {
|
||||||
groups: [],
|
groups: [],
|
||||||
unread: 0,
|
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
hasMore: false,
|
hasMore: false,
|
||||||
readMarkerId: '0',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function removeNotificationsForAccounts(
|
function removeNotificationsForAccounts(
|
||||||
|
@ -172,7 +168,6 @@ export const notificationsGroupsReducer =
|
||||||
})
|
})
|
||||||
.addCase(clearNotifications.pending, (state) => {
|
.addCase(clearNotifications.pending, (state) => {
|
||||||
state.groups = [];
|
state.groups = [];
|
||||||
state.unread = 0;
|
|
||||||
state.hasMore = false;
|
state.hasMore = false;
|
||||||
})
|
})
|
||||||
.addCase(blockAccountSuccess, (state, action) => {
|
.addCase(blockAccountSuccess, (state, action) => {
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import { compareId } from 'mastodon/compare_id';
|
||||||
|
import type { RootState } from 'mastodon/store';
|
||||||
|
|
||||||
|
export const selectUnreadNotificationsGroupsCount = createSelector(
|
||||||
|
[
|
||||||
|
(s: RootState) => s.markers.notifications,
|
||||||
|
(s: RootState) => s.notificationsGroups.groups,
|
||||||
|
],
|
||||||
|
(notificationMarker, groups) => {
|
||||||
|
return groups.filter(
|
||||||
|
(group) =>
|
||||||
|
group.type !== 'gap' &&
|
||||||
|
group.page_max_id &&
|
||||||
|
compareId(group.page_max_id, notificationMarker) > 0,
|
||||||
|
).length;
|
||||||
|
},
|
||||||
|
);
|
Loading…
Reference in New Issue