feat: 💨

This commit is contained in:
codytseng
2025-02-10 22:27:58 +08:00
parent 9c0e30ec24
commit 05cade1f99
3 changed files with 124 additions and 22 deletions

View File

@@ -1,4 +1,5 @@
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
import { PICTURE_EVENT_KIND } from '@/constants' import { PICTURE_EVENT_KIND } from '@/constants'
import { isReplyNoteEvent } from '@/lib/event' import { isReplyNoteEvent } from '@/lib/event'
import { checkAlgoRelay } from '@/lib/relay' import { checkAlgoRelay } from '@/lib/relay'
@@ -187,14 +188,14 @@ export default function NoteList({
}} }}
pullingContent="" pullingContent=""
> >
<div className="space-y-2 sm:space-y-2"> <div>
{newEvents.filter((event: Event) => { {newEvents.filter((event: Event) => {
return ( return (
(!filterMutedNotes || !mutePubkeys.includes(event.pubkey)) && (!filterMutedNotes || !mutePubkeys.includes(event.pubkey)) &&
(listMode !== 'posts' || !isReplyNoteEvent(event)) (listMode !== 'posts' || !isReplyNoteEvent(event))
) )
}).length > 0 && ( }).length > 0 && (
<div className="flex justify-center w-full mt-2"> <div className="flex justify-center w-full my-2">
<Button size="lg" onClick={showNewEvents}> <Button size="lg" onClick={showNewEvents}>
{t('show new notes')} {t('show new notes')}
</Button> </Button>
@@ -202,7 +203,7 @@ export default function NoteList({
)} )}
{isPictures ? ( {isPictures ? (
<PictureNoteCardMasonry <PictureNoteCardMasonry
className="px-2 sm:px-4 pt-2" className="px-2 sm:px-4 mt-2"
columnCount={isLargeScreen ? 3 : 2} columnCount={isLargeScreen ? 3 : 2}
events={events} events={events}
/> />
@@ -220,11 +221,12 @@ export default function NoteList({
))} ))}
</div> </div>
)} )}
<div className="text-center text-sm text-muted-foreground">
{hasMore || refreshing ? ( {hasMore || refreshing ? (
<div ref={bottomRef}>{t('loading...')}</div> <div ref={bottomRef}>
<LoadingSkeleton isPictures={isPictures} />
</div>
) : events.length ? ( ) : events.length ? (
t('no more notes') <div className="text-center text-sm text-muted-foreground mt-2">t('no more notes')</div>
) : ( ) : (
<div className="flex justify-center w-full mt-2"> <div className="flex justify-center w-full mt-2">
<Button size="lg" onClick={() => setRefreshCount((pre) => pre + 1)}> <Button size="lg" onClick={() => setRefreshCount((pre) => pre + 1)}>
@@ -233,7 +235,6 @@ export default function NoteList({
</div> </div>
)} )}
</div> </div>
</div>
</PullToRefresh> </PullToRefresh>
</div> </div>
) )
@@ -320,3 +321,45 @@ function PictureNoteCardMasonry({
</div> </div>
) )
} }
function LoadingSkeleton({ isPictures }: { isPictures: boolean }) {
const { isLargeScreen } = useScreenSize()
if (isPictures) {
return (
<div
className={cn(
'px-2 sm:px-4 grid',
isLargeScreen ? 'grid-cols-3 gap-4' : 'grid-cols-2 gap-2'
)}
>
{[...Array(isLargeScreen ? 3 : 2)].map((_, i) => (
<div key={i}>
<Skeleton className="rounded-lg w-full aspect-[6/8]" />
<div className="p-2">
<Skeleton className="w-32 h-5" />
<div className="flex items-center gap-2 mt-2">
<Skeleton className="w-5 h-5 rounded-full" />
<Skeleton className="w-16 h-3" />
</div>
</div>
</div>
))}
</div>
)
}
return (
<div className="px-4 py-3">
<div className="flex items-center space-x-2">
<Skeleton className="w-10 h-10 rounded-full" />
<div className="space-y-1">
<Skeleton className="w-10 h-4" />
<Skeleton className="w-20 h-3" />
</div>
</div>
<Skeleton className="w-full h-5 mt-2" />
<Skeleton className="w-2/3 h-5 mt-2" />
</div>
)
}

View File

@@ -1,3 +1,4 @@
import { Skeleton } from '@/components/ui/skeleton'
import { COMMENT_EVENT_KIND, PICTURE_EVENT_KIND } from '@/constants' import { COMMENT_EVENT_KIND, PICTURE_EVENT_KIND } from '@/constants'
import { useFetchEvent } from '@/hooks' import { useFetchEvent } from '@/hooks'
import { extractEmbeddedNotesFromContent, extractImagesFromContent } from '@/lib/event' import { extractEmbeddedNotesFromContent, extractImagesFromContent } from '@/lib/event'
@@ -9,7 +10,15 @@ import client from '@/services/client.service'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Heart, MessageCircle, Repeat, ThumbsUp } from 'lucide-react' import { Heart, MessageCircle, Repeat, ThumbsUp } from 'lucide-react'
import { Event, kinds, nip19, validateEvent } from 'nostr-tools' import { Event, kinds, nip19, validateEvent } from 'nostr-tools'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState
} from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import PullToRefresh from 'react-simple-pull-to-refresh' import PullToRefresh from 'react-simple-pull-to-refresh'
import { embedded, embeddedNostrNpubRenderer, embeddedNostrProfileRenderer } from '../Embedded' import { embedded, embeddedNostrNpubRenderer, embeddedNostrProfileRenderer } from '../Embedded'
@@ -18,7 +27,7 @@ import UserAvatar from '../UserAvatar'
const LIMIT = 100 const LIMIT = 100
export default function NotificationList() { const NotificationList = forwardRef((_, ref) => {
const { t } = useTranslation() const { t } = useTranslation()
const { pubkey } = useNostr() const { pubkey } = useNostr()
const [refreshCount, setRefreshCount] = useState(0) const [refreshCount, setRefreshCount] = useState(0)
@@ -27,6 +36,16 @@ export default function NotificationList() {
const [notifications, setNotifications] = useState<Event[]>([]) const [notifications, setNotifications] = useState<Event[]>([])
const [until, setUntil] = useState<number | undefined>(dayjs().unix()) const [until, setUntil] = useState<number | undefined>(dayjs().unix())
const bottomRef = useRef<HTMLDivElement | null>(null) const bottomRef = useRef<HTMLDivElement | null>(null)
useImperativeHandle(
ref,
() => ({
refresh: () => {
if (refreshing) return
setRefreshCount((count) => count + 1)
}
}),
[refreshing]
)
useEffect(() => { useEffect(() => {
if (!pubkey) { if (!pubkey) {
@@ -133,7 +152,33 @@ export default function NotificationList() {
))} ))}
<div className="text-center text-sm text-muted-foreground"> <div className="text-center text-sm text-muted-foreground">
{until || refreshing ? ( {until || refreshing ? (
<div ref={bottomRef}>{t('loading...')}</div> <div ref={bottomRef}>
<div className="flex gap-2 items-center h-11 py-2">
<Skeleton className="w-7 h-7 rounded-full" />
<Skeleton className="w-6 h-6 rounded-full" />
<Skeleton className="h-6 flex-1 w-0" />
</div>
<div className="flex gap-2 items-center h-11 py-2">
<Skeleton className="w-7 h-7 rounded-full" />
<Skeleton className="w-6 h-6 rounded-full" />
<Skeleton className="h-6 flex-1 w-0" />
</div>
<div className="flex gap-2 items-center h-11 py-2">
<Skeleton className="w-7 h-7 rounded-full" />
<Skeleton className="w-6 h-6 rounded-full" />
<Skeleton className="h-6 flex-1 w-0" />
</div>
<div className="flex gap-2 items-center h-11 py-2">
<Skeleton className="w-7 h-7 rounded-full" />
<Skeleton className="w-6 h-6 rounded-full" />
<Skeleton className="h-6 flex-1 w-0" />
</div>
<div className="flex gap-2 items-center h-11 py-2">
<Skeleton className="w-7 h-7 rounded-full" />
<Skeleton className="w-6 h-6 rounded-full" />
<Skeleton className="h-6 flex-1 w-0" />
</div>
</div>
) : ( ) : (
t('no more notifications') t('no more notifications')
)} )}
@@ -141,7 +186,9 @@ export default function NotificationList() {
</div> </div>
</PullToRefresh> </PullToRefresh>
) )
} })
NotificationList.displayName = 'NotificationList'
export default NotificationList
function NotificationItem({ notification }: { notification: Event }) { function NotificationItem({ notification }: { notification: Event }) {
if (notification.kind === kinds.Reaction) { if (notification.kind === kinds.Reaction) {

View File

@@ -1,10 +1,22 @@
import NotificationList from '@/components/NotificationList' import NotificationList from '@/components/NotificationList'
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout' import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
import { usePrimaryPage } from '@/PageManager'
import { Bell } from 'lucide-react' import { Bell } from 'lucide-react'
import { forwardRef } from 'react' import { forwardRef, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
const NotificationListPage = forwardRef((_, ref) => { const NotificationListPage = forwardRef((_, ref) => {
const { current } = usePrimaryPage()
const firstRenderRef = useRef(true)
const notificationListRef = useRef<{ refresh: () => void }>(null)
useEffect(() => {
if (current === 'notifications' && !firstRenderRef.current) {
notificationListRef.current?.refresh()
}
firstRenderRef.current = false
}, [current])
return ( return (
<PrimaryPageLayout <PrimaryPageLayout
ref={ref} ref={ref}
@@ -13,7 +25,7 @@ const NotificationListPage = forwardRef((_, ref) => {
displayScrollToTopButton displayScrollToTopButton
> >
<div className="px-4"> <div className="px-4">
<NotificationList /> <NotificationList ref={notificationListRef} />
</div> </div>
</PrimaryPageLayout> </PrimaryPageLayout>
) )