From 4a143d18143a903c360e4e0bb54a4aaae870c86e Mon Sep 17 00:00:00 2001 From: codytseng Date: Sat, 12 Apr 2025 23:04:52 +0800 Subject: [PATCH] feat: show number of new notifications --- src/components/NotificationList/index.tsx | 11 +++-- src/providers/NotificationProvider.tsx | 52 +++++++++++++++-------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/components/NotificationList/index.tsx b/src/components/NotificationList/index.tsx index d81a5eff..04dd5ac9 100644 --- a/src/components/NotificationList/index.tsx +++ b/src/components/NotificationList/index.tsx @@ -2,12 +2,12 @@ import { Separator } from '@/components/ui/separator' import { Skeleton } from '@/components/ui/skeleton' import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' import { cn } from '@/lib/utils' +import { usePrimaryPage } from '@/PageManager' import { useDeepBrowsing } from '@/providers/DeepBrowsingProvider' import { useNostr } from '@/providers/NostrProvider' import { useNoteStats } from '@/providers/NoteStatsProvider' import { useNotification } from '@/providers/NotificationProvider' import client from '@/services/client.service' -import storage from '@/services/local-storage.service' import { TNotificationType } from '@/types' import dayjs from 'dayjs' import { Event, kinds } from 'nostr-tools' @@ -21,8 +21,9 @@ const SHOW_COUNT = 30 const NotificationList = forwardRef((_, ref) => { const { t } = useTranslation() + const { current } = usePrimaryPage() const { pubkey } = useNostr() - const { clearNewNotifications } = useNotification() + const { clearNewNotifications, getNotificationsSeenAt } = useNotification() const { updateNoteStatsByEvents } = useNoteStats() const [notificationType, setNotificationType] = useState('all') const [lastReadTime, setLastReadTime] = useState(0) @@ -59,6 +60,8 @@ const NotificationList = forwardRef((_, ref) => { ) useEffect(() => { + if (current !== 'notifications') return + if (!pubkey) { setUntil(undefined) return @@ -68,7 +71,7 @@ const NotificationList = forwardRef((_, ref) => { setLoading(true) setNotifications([]) setShowCount(SHOW_COUNT) - setLastReadTime(storage.getLastReadNotificationTime(pubkey)) + setLastReadTime(getNotificationsSeenAt()) clearNewNotifications() const relayList = await client.fetchRelayList(pubkey) @@ -113,7 +116,7 @@ const NotificationList = forwardRef((_, ref) => { return () => { promise.then((closer) => closer?.()) } - }, [pubkey, refreshCount, filterKinds]) + }, [pubkey, refreshCount, filterKinds, current]) useEffect(() => { const visibleNotifications = notifications.slice(0, showCount) diff --git a/src/providers/NotificationProvider.tsx b/src/providers/NotificationProvider.tsx index 7dafdab1..6c776917 100644 --- a/src/providers/NotificationProvider.tsx +++ b/src/providers/NotificationProvider.tsx @@ -2,12 +2,13 @@ import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' import client from '@/services/client.service' import { kinds } from 'nostr-tools' import { SubCloser } from 'nostr-tools/abstract-pool' -import { createContext, useContext, useEffect, useState } from 'react' +import { createContext, useContext, useEffect, useRef, useState } from 'react' import { useMuteList } from './MuteListProvider' import { useNostr } from './NostrProvider' type TNotificationContext = { hasNewNotification: boolean + getNotificationsSeenAt: () => number clearNewNotifications: () => Promise } @@ -24,16 +25,16 @@ export const useNotification = () => { export function NotificationProvider({ children }: { children: React.ReactNode }) { const { pubkey, notificationsSeenAt, updateNotificationsSeenAt } = useNostr() const { mutePubkeys } = useMuteList() - const [hasNewNotification, setHasNewNotification] = useState(false) + const [newNotificationIds, setNewNotificationIds] = useState(new Set()) + const subCloserRef = useRef(null) useEffect(() => { if (!pubkey || notificationsSeenAt < 0) return - setHasNewNotification(false) + setNewNotificationIds(new Set()) // Track if component is mounted const isMountedRef = { current: true } - let currentSubCloser: SubCloser | null = null const subscribe = async () => { if (!isMountedRef.current) return null @@ -54,15 +55,14 @@ export function NotificationProvider({ children }: { children: React.ReactNode } ], '#p': [pubkey], since: notificationsSeenAt, - limit: 10 + limit: 20 } ], { onevent: (evt) => { // Only show notification if not from self and not muted if (evt.pubkey !== pubkey && !mutePubkeys.includes(evt.pubkey)) { - setHasNewNotification(true) - subCloser.close() + setNewNotificationIds((prev) => new Set([...prev, evt.id])) } }, onclose: (reasons) => { @@ -71,7 +71,7 @@ export function NotificationProvider({ children }: { children: React.ReactNode } } // Only reconnect if still mounted and not a manual close - if (isMountedRef.current && currentSubCloser) { + if (isMountedRef.current && subCloserRef.current) { setTimeout(() => { if (isMountedRef.current) { subscribe() @@ -82,7 +82,7 @@ export function NotificationProvider({ children }: { children: React.ReactNode } } ) - currentSubCloser = subCloser + subCloserRef.current = subCloser return subCloser } catch (error) { console.error('Subscription error:', error) @@ -105,30 +105,48 @@ export function NotificationProvider({ children }: { children: React.ReactNode } // Cleanup function return () => { isMountedRef.current = false - if (currentSubCloser) { - currentSubCloser.close() - currentSubCloser = null + if (subCloserRef.current) { + subCloserRef.current.close() + subCloserRef.current = null } } }, [notificationsSeenAt, pubkey]) useEffect(() => { - if (hasNewNotification) { - document.title = '📥 Jumble' + if (newNotificationIds.size >= 10 && subCloserRef.current) { + subCloserRef.current.close() + subCloserRef.current = null + } + }, [newNotificationIds]) + + useEffect(() => { + const newNotificationCount = newNotificationIds.size + if (newNotificationCount > 0) { + document.title = `(${newNotificationCount >= 10 ? '9+' : newNotificationCount}) Jumble` } else { document.title = 'Jumble' } - }, [hasNewNotification]) + }, [newNotificationIds]) + + const getNotificationsSeenAt = () => { + return notificationsSeenAt + } const clearNewNotifications = async () => { if (!pubkey) return - setHasNewNotification(false) + setNewNotificationIds(new Set()) await updateNotificationsSeenAt() } return ( - + 0, + clearNewNotifications, + getNotificationsSeenAt + }} + > {children} )