diff --git a/src/services/read-status.ts b/src/services/read-status.ts index c9458d377..e3b0d6324 100644 --- a/src/services/read-status.ts +++ b/src/services/read-status.ts @@ -24,8 +24,8 @@ class ReadStatusService { if (ttl) this.setTTL(key, ttl); else this.setTTL(key, dayjs().add(1, "day").unix()); - if (subject.value === undefined && !this.queue.has(key)) { - this.queue.add(key); + if (subject.value === undefined && !this.readQueue.has(key)) { + this.readQueue.add(key); this.throttleRead(); } @@ -37,51 +37,60 @@ class ReadStatusService { else this.setTTL(key, dayjs().add(1, "day").unix()); this.status.get(key).next(read); + this.writeQueue.add(key); this.throttleWrite(); } - queue = new Set(); - private throttleRead = _throttle(this.read.bind(this), 1000); + private readQueue = new Set(); + private throttleRead = _throttle(this.read.bind(this), 100); async read() { - if (this.queue.size === 0) return; + if (this.readQueue.size === 0) return; const trans = db.transaction("read"); - this.log(`Loading ${this.queue.size} from database`); + this.log(`Loading ${this.readQueue.size} from database`); await Promise.all( - Array.from(this.queue).map(async (key) => { + Array.from(this.readQueue).map(async (key) => { + this.readQueue.delete(key); const subject = this.status.get(key); const status = await trans.store.get(key); + + this.log(key, status); + if (status) { subject.next(status.read); if (status.ttl) this.setTTL(key, status.ttl); } else subject.next(false); }), ); - this.queue.clear(); } - throttleWrite = _throttle(this.write.bind(this), 1000); + private writeQueue = new Set(); + private throttleWrite = _throttle(this.write.bind(this), 100); async write() { + if (this.writeQueue.size === 0) return; + const trans = db.transaction("read", "readwrite"); let count = 0; const defaultTTL = dayjs().add(1, "day").unix(); - for (const [key, subject] of this.status) { + for (const key of this.writeQueue) { + const subject = this.status.get(key); if (subject.value !== undefined) { trans.store.put({ key, read: subject.value, ttl: this.ttl.get(key) ?? defaultTTL }); count++; } } + this.writeQueue.clear(); await trans.done; this.log(`Wrote ${count} to database`); } async prune() { - const expired = await db.getAllKeysFromIndex("read", "ttl", IDBKeyRange.lowerBound(dayjs().unix(), true)); + const expired = await db.getAllKeysFromIndex("read", "ttl", IDBKeyRange.upperBound(dayjs().unix())); if (expired.length === 0) return; @@ -95,7 +104,6 @@ class ReadStatusService { const readStatusService = new ReadStatusService(); -setInterval(readStatusService.write.bind(readStatusService), 10_000); setInterval(readStatusService.prune.bind(readStatusService), 30_000); if (import.meta.env.DEV) { diff --git a/src/views/notifications/components/notification-icon-entry.tsx b/src/views/notifications/components/notification-icon-entry.tsx index 2dc8470e8..116004db9 100644 --- a/src/views/notifications/components/notification-icon-entry.tsx +++ b/src/views/notifications/components/notification-icon-entry.tsx @@ -1,5 +1,7 @@ -import { Box, Flex, Spacer, Text, useColorModeValue } from "@chakra-ui/react"; import { PropsWithChildren, ReactNode, forwardRef, memo, useCallback, useContext, useEffect } from "react"; +import { Box, Flex, Spacer, Text, useColorModeValue } from "@chakra-ui/react"; +import dayjs from "dayjs"; + import UserAvatar from "../../../components/user/user-avatar"; import Timestamp from "../../../components/timestamp"; import UserName from "../../../components/user/user-name"; @@ -7,7 +9,7 @@ import { CheckIcon } from "../../../components/icons"; import FocusedContext from "../focused-context"; import useReadStatus from "../../../hooks/use-read-status"; -const ONE_MONTH = 60 * 60 * 24 * 30; +const ONE_MONTH = dayjs().add(1, "month").unix(); type NotificationIconEntryProps = PropsWithChildren<{ icon: ReactNode; @@ -54,7 +56,7 @@ const NotificationIconEntry = memo( onFocus={onClick ? undefined : focusSelf} onClick={onClick} userSelect="none" - bg={expanded || !read ? focusColor : undefined} + bg={!read ? focusColor : undefined} > {icon} diff --git a/src/views/notifications/index.tsx b/src/views/notifications/index.tsx index 4c19ca4cf..a8afd037c 100644 --- a/src/views/notifications/index.tsx +++ b/src/views/notifications/index.tsx @@ -103,16 +103,17 @@ const NotificationsTimeline = memo( const navigateNextUnread = () => { const focusedEvent = filteredEvents.find((e) => e.id === focused); - if (focusedEvent) { - const idx = filteredEvents.indexOf(focusedEvent); - for (let i = idx; i < filteredEvents.length; i++) { - if (readStatusService.getStatus(filteredEvents[i].id).value === false) { - setFocus(filteredEvents[i].id); - break; - } + const idx = focusedEvent ? filteredEvents.indexOf(focusedEvent) : 0; + for (let i = idx; i < filteredEvents.length; i++) { + if (readStatusService.getStatus(filteredEvents[i].id).value === false) { + setFocus(filteredEvents[i].id); + break; } } }; + const navigateTop = () => setFocus(filteredEvents[0]?.id ?? ""); + const navigateEnd = () => setFocus(filteredEvents[filteredEvents.length - 1]?.id ?? ""); + useKeyPressEvent("ArrowUp", navigatePrev); useKeyPressEvent("ArrowDown", navigateNext); useKeyPressEvent("ArrowLeft", navigatePrev); @@ -121,8 +122,10 @@ const NotificationsTimeline = memo( useKeyPressEvent("h", navigatePrev); useKeyPressEvent("j", navigateNext); useKeyPressEvent("l", navigateNextUnread); - useKeyPressEvent("H", () => setFocus(filteredEvents[0]?.id ?? "")); - useKeyPressEvent("L", () => setFocus(filteredEvents[filteredEvents.length - 1]?.id ?? "")); + useKeyPressEvent("H", navigateTop); + useKeyPressEvent("Home", navigateTop); + useKeyPressEvent("L", navigateEnd); + useKeyPressEvent("End", navigateEnd); if (filteredEvents.length === 0) return ( diff --git a/src/views/notifications/threads.tsx b/src/views/notifications/threads.tsx index 8d29849f7..d504d4443 100644 --- a/src/views/notifications/threads.tsx +++ b/src/views/notifications/threads.tsx @@ -12,8 +12,7 @@ import { useNotifications } from "../../providers/global/notifications-provider" import { TORRENT_COMMENT_KIND } from "../../helpers/nostr/torrents"; import { groupByRoot } from "../../helpers/notification"; import { NostrEvent } from "../../types/nostr-event"; -import NotificationIconEntry from "./components/notification-icon-entry"; -import { ChevronLeftIcon, ReplyIcon } from "../../components/icons"; +import { ChevronLeftIcon } from "../../components/icons"; import { AvatarGroup, Box, Button, ButtonGroup, Flex, LinkBox, Text, useDisclosure } from "@chakra-ui/react"; import UserAvatarLink from "../../components/user/user-avatar-link"; import useSingleEvent from "../../hooks/use-single-event"; @@ -27,6 +26,7 @@ import { useNavigateInDrawer } from "../../providers/drawer-sub-view-provider"; import useEventIntersectionRef from "../../hooks/use-event-intersection-ref"; import useShareableEventAddress from "../../hooks/use-shareable-event-address"; import localSettings from "../../services/local-settings"; +import GitBranch01 from "../../components/icons/git-branch-01"; const THREAD_KINDS = [kinds.ShortTextNote, TORRENT_COMMENT_KIND]; @@ -67,29 +67,32 @@ function ThreadGroup({ rootId, events }: { rootId: string; events: NostrEvent[] const ref = useEventIntersectionRef(events[events.length - 1]); return ( - }> - - {pubkeys.map((pubkey) => ( - + + + + + {pubkeys.map((pubkey) => ( + + ))} + + + + {pubkeys.length > 1 ? pubkeys.length + " people" : pubkeys.length + " person"} replied in thread: + + {rootEvent && } + + {(events.length > 3 && !showAll.isOpen ? events.slice(0, 3) : events).map((event) => ( + ))} - - - - {pubkeys.length > 1 ? pubkeys.length + " people" : pubkeys.length + " person"} replied in thread: - - {rootEvent && } - - {(events.length > 3 && !showAll.isOpen ? events.slice(0, 3) : events).map((event) => ( - - ))} - {!showAll.isOpen && events.length > 3 && ( - - - - )} - + {!showAll.isOpen && events.length > 3 && ( + + + + )} + + ); } @@ -118,7 +121,7 @@ function ThreadsNotificationsPage() { -