diff --git a/packages/pl-fe/src/actions/notifications.ts b/packages/pl-fe/src/actions/notifications.ts index c03e37e97..2899bce65 100644 --- a/packages/pl-fe/src/actions/notifications.ts +++ b/packages/pl-fe/src/actions/notifications.ts @@ -24,7 +24,6 @@ import type { AppDispatch, RootState } from 'pl-fe/store'; const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE' as const; const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP' as const; const NOTIFICATIONS_UPDATE_QUEUE = 'NOTIFICATIONS_UPDATE_QUEUE' as const; -const NOTIFICATIONS_DEQUEUE = 'NOTIFICATIONS_DEQUEUE' as const; const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST' as const; const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS' as const; @@ -100,8 +99,6 @@ const updateNotificationsQueue = (notification: BaseNotification, intlMessages: let filtered: boolean | null = false; - const isOnNotificationsPage = curPath === '/notifications'; - if (notification.type === 'mention' || notification.type === 'status') { const regex = regexFromFilters(filters); const searchIndex = notification.status.spoiler_text + '\n' + unescapeHTML(notification.status.content); @@ -139,37 +136,7 @@ const updateNotificationsQueue = (notification: BaseNotification, intlMessages: }); } - if (isOnNotificationsPage) { - dispatch({ - type: NOTIFICATIONS_UPDATE_QUEUE, - notification, - intlMessages, - intlLocale, - }); - } else { - dispatch(updateNotifications(notification)); - } - }; - -const dequeueNotifications = () => - (dispatch: AppDispatch, getState: () => RootState) => { - const queuedNotifications = getState().notifications.queuedNotifications; - const totalQueuedNotificationsCount = getState().notifications.totalQueuedNotificationsCount; - - if (totalQueuedNotificationsCount === 0) { - return; - } else if (totalQueuedNotificationsCount > 0 && totalQueuedNotificationsCount <= MAX_QUEUED_NOTIFICATIONS) { - queuedNotifications.forEach((block) => { - dispatch(updateNotifications(block.notification)); - }); - } else { - dispatch(expandNotifications()); - } - - dispatch({ - type: NOTIFICATIONS_DEQUEUE, - }); - dispatch(markReadNotifications()); + dispatch(updateNotifications(notification)); }; const excludeTypesFromFilter = (filters: string[]) => NOTIFICATION_TYPES.filter(item => !filters.includes(item)); @@ -295,7 +262,6 @@ export { NOTIFICATIONS_UPDATE, NOTIFICATIONS_UPDATE_NOOP, NOTIFICATIONS_UPDATE_QUEUE, - NOTIFICATIONS_DEQUEUE, NOTIFICATIONS_EXPAND_REQUEST, NOTIFICATIONS_EXPAND_SUCCESS, NOTIFICATIONS_EXPAND_FAIL, @@ -309,7 +275,6 @@ export { type FilterType, updateNotifications, updateNotificationsQueue, - dequeueNotifications, expandNotifications, expandNotificationsRequest, expandNotificationsSuccess, diff --git a/packages/pl-fe/src/features/notifications/index.tsx b/packages/pl-fe/src/features/notifications/index.tsx index dcb5ef7fa..9d4f1e31d 100644 --- a/packages/pl-fe/src/features/notifications/index.tsx +++ b/packages/pl-fe/src/features/notifications/index.tsx @@ -1,13 +1,13 @@ import clsx from 'clsx'; import debounce from 'lodash/debounce'; -import React, { useCallback, useEffect, useRef } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { createSelector } from 'reselect'; import { expandNotifications, + markReadNotifications, scrollTopNotifications, - dequeueNotifications, } from 'pl-fe/actions/notifications'; import PullToRefresh from 'pl-fe/components/pull-to-refresh'; import ScrollTopButton from 'pl-fe/components/scroll-top-button'; @@ -31,7 +31,27 @@ const messages = defineMessages({ const getNotifications = createSelector([ (state: RootState) => state.notifications.items.toArray(), -], (notifications) => notifications.map(([_, notification]) => notification).filter(item => item !== null)); + (_, topNotification?: string) => topNotification, +], (notifications, topNotificationId) => { + const allNotifications = notifications.map(([_, notification]) => notification).filter(item => item !== null); + + if (topNotificationId) { + const queuedNotificationCount = allNotifications.findIndex((notification) => + notification.most_recent_notification_id <= topNotificationId, + ); + const displayedNotifications = allNotifications.slice(queuedNotificationCount); + + return { + queuedNotificationCount, + displayedNotifications, + }; + } + + return { + queuedNotificationCount: 0, + displayedNotifications: allNotifications, + }; +}); const Notifications = () => { const dispatch = useAppDispatch(); @@ -40,11 +60,11 @@ const Notifications = () => { const showFilterBar = settings.notifications.quickFilter.show; const activeFilter = settings.notifications.quickFilter.active; - const notifications = useAppSelector(state => getNotifications(state)); + const [topNotification, setTopNotification] = useState(); + const { queuedNotificationCount, displayedNotifications } = useAppSelector(state => getNotifications(state, topNotification)); const isLoading = useAppSelector(state => state.notifications.isLoading); // const isUnread = useAppSelector(state => state.notifications.unread > 0); const hasMore = useAppSelector(state => state.notifications.hasMore); - const totalQueuedNotificationsCount = useAppSelector(state => state.notifications.totalQueuedNotificationsCount || 0); const scrollableContentRef = useRef | null>(null); @@ -53,26 +73,26 @@ const Notifications = () => { // }; const handleLoadOlder = useCallback(debounce(() => { - const minId = notifications.reduce( + const minId = displayedNotifications.reduce( (minId, notification) => minId && notification.page_min_id && notification.page_min_id > minId ? minId : notification.page_min_id, undefined, ); dispatch(expandNotifications({ maxId: minId })); - }, 300, { leading: true }), [notifications]); + }, 300, { leading: true }), [displayedNotifications]); const handleScroll = useCallback(debounce((startIndex?: number) => { dispatch(scrollTopNotifications(startIndex === 0)); }, 100), []); const handleMoveUp = (id: string) => { - const elementIndex = notifications.findIndex(item => item !== null && item.group_key === id) - 1; + const elementIndex = displayedNotifications.findIndex(item => item !== null && item.group_key === id) - 1; _selectChild(elementIndex); }; const handleMoveDown = (id: string) => { - const elementIndex = notifications.findIndex(item => item !== null && item.group_key === id) + 1; + const elementIndex = displayedNotifications.findIndex(item => item !== null && item.group_key === id) + 1; _selectChild(elementIndex); }; @@ -84,7 +104,8 @@ const Notifications = () => { }; const handleDequeueNotifications = useCallback(() => { - dispatch(dequeueNotifications()); + setTopNotification(undefined); + dispatch(markReadNotifications()); }, []); const handleRefresh = useCallback(() => dispatch(expandNotifications()), []); @@ -100,6 +121,11 @@ const Notifications = () => { }; }, []); + useEffect(() => { + if (topNotification || displayedNotifications.length === 0) return; + setTopNotification(displayedNotifications[0].most_recent_notification_id); + }, [displayedNotifications.length]); + const emptyMessage = activeFilter === 'all' ? : ; @@ -112,8 +138,8 @@ const Notifications = () => { if (isLoading && scrollableContentRef.current) { scrollableContent = scrollableContentRef.current; - } else if (notifications.length > 0 || hasMore) { - scrollableContent = notifications.map((item) => ( + } else if (displayedNotifications.length > 0 || hasMore) { + scrollableContent = displayedNotifications.map((item) => ( { const scrollContainer = ( { onLoadMore={handleLoadOlder} onScroll={handleScroll} listClassName={clsx('divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800', { - 'animate-pulse': notifications.length === 0, + 'animate-pulse': displayedNotifications.length === 0, })} > {scrollableContent!} @@ -152,7 +178,7 @@ const Notifications = () => { diff --git a/packages/pl-fe/src/reducers/notifications.ts b/packages/pl-fe/src/reducers/notifications.ts index 6b907ed99..7c50f7a48 100644 --- a/packages/pl-fe/src/reducers/notifications.ts +++ b/packages/pl-fe/src/reducers/notifications.ts @@ -20,41 +20,29 @@ import { NOTIFICATIONS_FILTER_SET, NOTIFICATIONS_CLEAR, NOTIFICATIONS_SCROLL_TOP, - NOTIFICATIONS_UPDATE_QUEUE, - NOTIFICATIONS_DEQUEUE, NOTIFICATIONS_MARK_READ_REQUEST, - MAX_QUEUED_NOTIFICATIONS, } from '../actions/notifications'; import { TIMELINE_DELETE, type TimelineAction } from '../actions/timelines'; import type { Notification as BaseNotification, Markers, NotificationGroup, PaginatedResponse, Relationship } from 'pl-api'; import type { AnyAction } from 'redux'; -const QueuedNotificationRecord = ImmutableRecord({ - notification: {} as any as BaseNotification, - intlMessages: {} as Record, - intlLocale: '', -}); - const ReducerRecord = ImmutableRecord({ items: ImmutableOrderedMap(), hasMore: true, top: false, unread: 0, isLoading: false, - queuedNotifications: ImmutableOrderedMap(), //max = MAX_QUEUED_NOTIFICATIONS - totalQueuedNotificationsCount: 0, //used for queuedItems overflow for MAX_QUEUED_NOTIFICATIONS+ lastRead: -1 as string | -1, }); type State = ReturnType; -type QueuedNotification = ReturnType; const parseId = (id: string | number) => parseInt(id as string, 10); // For sorting the notifications -const comparator = (a: Pick, b: Pick) => { - const parse = (m: Pick) => parseId(m.group_key); +const comparator = (a: Pick, b: Pick) => { + const parse = (m: Pick) => parseId(m.most_recent_notification_id); if (parse(a) < parse(b)) return 1; if (parse(a) > parse(b)) return -1; return 0; @@ -75,13 +63,7 @@ const importNotification = (state: State, notification: NotificationGroup) => { if (!top) state = state.update('unread', unread => unread + 1); - return state.update('items', map => { - if (top && map.size > 40) { - map = map.take(20); - } - - return map.set(notification.group_key, notification).sort(comparator); - }); + return state.update('items', map => map.set(notification.group_key, notification).sort(comparator)); }; const expandNormalizedNotifications = (state: State, notifications: NotificationGroup[], next: (() => Promise>) | null) => { @@ -112,28 +94,6 @@ const deleteByStatus = (state: State, statusId: string) => // @ts-ignore state.update('items', map => map.filterNot(item => item !== null && item.status === statusId)); -const updateNotificationsQueue = (state: State, notification: BaseNotification, intlMessages: Record, intlLocale: string) => { - const queuedNotifications = state.queuedNotifications; - const listedNotifications = state.items; - const totalQueuedNotificationsCount = state.totalQueuedNotificationsCount; - - const alreadyExists = queuedNotifications.has(notification.group_key) || listedNotifications.has(notification.group_key); - if (alreadyExists) return state; - - const newQueuedNotifications = queuedNotifications; - - return state.withMutations(mutable => { - if (totalQueuedNotificationsCount <= MAX_QUEUED_NOTIFICATIONS) { - mutable.set('queuedNotifications', newQueuedNotifications.set(notification.group_key, QueuedNotificationRecord({ - notification, - intlMessages, - intlLocale, - }))); - } - mutable.set('totalQueuedNotificationsCount', totalQueuedNotificationsCount + 1); - }); -}; - const importMarker = (state: State, marker: Markers) => { const lastReadId = marker.notifications.last_read_id || -1 as string | -1; @@ -163,13 +123,6 @@ const notifications = (state: State = ReducerRecord(), action: AccountsAction | return updateTop(state, action.top); case NOTIFICATIONS_UPDATE: return importNotification(state, action.notification); - case NOTIFICATIONS_UPDATE_QUEUE: - return updateNotificationsQueue(state, action.notification, action.intlMessages, action.intlLocale); - case NOTIFICATIONS_DEQUEUE: - return state.withMutations(mutable => { - mutable.delete('queuedNotifications'); - mutable.set('totalQueuedNotificationsCount', 0); - }); case NOTIFICATIONS_EXPAND_SUCCESS: return expandNormalizedNotifications(state, action.notifications, action.next); case ACCOUNT_BLOCK_SUCCESS: