refactor: new notes button

This commit is contained in:
codytseng
2025-04-14 12:41:57 +08:00
parent 8c141bbbe4
commit 1c3e54c895
16 changed files with 82 additions and 102 deletions

View File

@@ -1,84 +1,68 @@
import React, { useState, useEffect } from 'react' import { Button } from '@/components/ui/button'
import UserAvatar from '@/components/UserAvatar' import { SimpleUserAvatar } from '@/components/UserAvatar'
import { cn } from '@/lib/utils'
import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { useDeepBrowsing } from '@/providers/DeepBrowsingProvider' import { Event } from 'nostr-tools'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
export type User = { export default function NewNotesButton({
pubkey: string newEvents = [],
} onClick
}: {
interface NewNotesButtonProps { newEvents?: Event[]
users?: User[] onClick?: () => void
eventCount?: number }) {
onShowEvents?: () => void const { t } = useTranslation()
}
const NewNotesButton: React.FC<NewNotesButtonProps> = ({
users = [],
eventCount: initialEventCount = 0,
onShowEvents
}) => {
const [newNotesCount, setNewNotesCount] = useState(initialEventCount)
const { isSmallScreen } = useScreenSize() const { isSmallScreen } = useScreenSize()
const { deepBrowsing } = useDeepBrowsing() const pubkeys = useMemo(() => {
const arr: string[] = []
useEffect(() => { for (const event of newEvents) {
setNewNotesCount(initialEventCount) if (!arr.includes(event.pubkey)) {
}, [initialEventCount]) arr.push(event.pubkey)
}
const handleClick = () => { if (arr.length >= 3) break
if (onShowEvents) {
onShowEvents()
} else {
console.log('Showing new notes...')
} }
setNewNotesCount(0) return arr
} }, [newEvents])
const getDesktopPosition = () => {
return deepBrowsing
? 'absolute left-0 right-0 top-[3.5rem] w-full flex justify-center z-50'
: 'absolute left-0 right-0 top-[6.5rem] w-full flex justify-center z-50'
}
return ( return (
<> <>
{newNotesCount > 0 && ( {newEvents.length > 0 && (
<div <div
className={ className={cn(
isSmallScreen 'w-full flex justify-center z-40',
? 'fixed left-0 right-0 w-full flex justify-center z-[9999]' isSmallScreen ? 'fixed' : 'absolute bottom-4'
: getDesktopPosition() )}
}
style={isSmallScreen ? { bottom: 'calc(4rem + env(safe-area-inset-bottom))' } : undefined} style={isSmallScreen ? { bottom: 'calc(4rem + env(safe-area-inset-bottom))' } : undefined}
> >
<button <Button
onClick={handleClick} onClick={onClick}
className="flex items-center bg-purple-600 hover:bg-purple-700 text-white px-3 py-2 rounded-full text-sm font-medium shadow-lg" className="group rounded-full h-fit pl-2 pr-3 hover:bg-primary-hover"
> >
{users && users.length > 0 && ( {pubkeys.length > 0 && (
<div className="flex items-center mr-1"> <div className="flex items-center">
{users.slice(0, 3).map((user, index) => ( {pubkeys.map((pubkey, index) => (
<div <div
key={user.pubkey} key={pubkey}
className="relative -mr-2.5 last:mr-0" className="relative -mr-2.5 last:mr-0"
style={{ zIndex: 3 - index }} style={{ zIndex: 3 - index }}
> >
<div className="w-7 h-7 rounded-full border-2 border-purple-600 overflow-hidden flex items-center justify-center bg-background"> <SimpleUserAvatar
<UserAvatar userId={user.pubkey} size="small" /> userId={pubkey}
</div> size="small"
className="border-primary border-2 group-hover:border-primary-hover"
/>
</div> </div>
))} ))}
</div> </div>
)} )}
<span className="whitespace-nowrap ml-1"> <div className="text-md font-medium">
Show {newNotesCount > 99 ? '99+' : newNotesCount} new events {t('Show n new notes', { n: newEvents.length > 99 ? '99+' : newEvents.length })}
</span> </div>
</button> </Button>
</div> </div>
)} )}
</> </>
) )
} }
export default NewNotesButton

View File

@@ -59,6 +59,14 @@ export default function NoteList({
} }
}, [JSON.stringify(filter), isPictures]) }, [JSON.stringify(filter), isPictures])
const topRef = useRef<HTMLDivElement | null>(null) const topRef = useRef<HTMLDivElement | null>(null)
const filteredNewEvents = useMemo(() => {
return newEvents.filter((event: Event) => {
return (
(!filterMutedNotes || !mutePubkeys.includes(event.pubkey)) &&
(listMode !== 'posts' || !isReplyNoteEvent(event))
)
})
}, [newEvents, listMode, filterMutedNotes, mutePubkeys])
useEffect(() => { useEffect(() => {
if (relayUrls.length === 0 && !noteFilter.authors?.length) return if (relayUrls.length === 0 && !noteFilter.authors?.length) return
@@ -168,29 +176,6 @@ export default function NoteList({
setNewEvents([]) setNewEvents([])
} }
const newUsers = useMemo(() => {
return newEvents
.filter((event: Event) => {
return (
(!filterMutedNotes || !mutePubkeys.includes(event.pubkey)) &&
(listMode !== 'posts' || !isReplyNoteEvent(event))
)
})
.slice(0, 3)
.map((event) => {
return {
pubkey: event.pubkey
}
})
}, [newEvents, filterMutedNotes, mutePubkeys, listMode])
const filteredNewEventsCount = newEvents.filter((event: Event) => {
return (
(!filterMutedNotes || !mutePubkeys.includes(event.pubkey)) &&
(listMode !== 'posts' || !isReplyNoteEvent(event))
)
}).length
return ( return (
<div className={className}> <div className={className}>
<ListModeSwitch <ListModeSwitch
@@ -203,12 +188,8 @@ export default function NoteList({
}} }}
/> />
<div ref={topRef} /> <div ref={topRef} />
{filteredNewEventsCount > 0 && ( {filteredNewEvents.length > 0 && (
<NewNotesButton <NewNotesButton newEvents={filteredNewEvents} onClick={showNewEvents} />
users={newUsers}
eventCount={filteredNewEventsCount}
onShowEvents={showNewEvents}
/>
)} )}
<PullToRefresh <PullToRefresh
onRefresh={async () => { onRefresh={async () => {

View File

@@ -216,6 +216,7 @@ export default {
'Media upload service': 'خدمة تحميل الوسائط', 'Media upload service': 'خدمة تحميل الوسائط',
'Choose a relay': 'اختر ريلاي', 'Choose a relay': 'اختر ريلاي',
'no relays found': 'لم يتم العثور على ريلايات', 'no relays found': 'لم يتم العثور على ريلايات',
video: 'فيديو' video: 'فيديو',
'Show n new notes': 'عرض {{n}} ملاحظات جديدة'
} }
} }

View File

@@ -220,6 +220,7 @@ export default {
'Media upload service': 'Medien-Upload-Service', 'Media upload service': 'Medien-Upload-Service',
'Choose a relay': 'Wähle ein Relay', 'Choose a relay': 'Wähle ein Relay',
'no relays found': 'Keine Relays gefunden', 'no relays found': 'Keine Relays gefunden',
video: 'Video' video: 'Video',
'Show n new notes': 'Zeige {{n}} neue Notizen'
} }
} }

View File

@@ -216,6 +216,7 @@ export default {
'Media upload service': 'Media upload service', 'Media upload service': 'Media upload service',
'Choose a relay': 'Choose a relay', 'Choose a relay': 'Choose a relay',
'no relays found': 'no relays found', 'no relays found': 'no relays found',
video: 'video' video: 'video',
'Show n new notes': 'Show {{n}} new notes'
} }
} }

View File

@@ -220,6 +220,7 @@ export default {
'Media upload service': 'Servicio de carga de medios', 'Media upload service': 'Servicio de carga de medios',
'Choose a relay': 'Selecciona un relé', 'Choose a relay': 'Selecciona un relé',
'no relays found': 'no se encontraron relés', 'no relays found': 'no se encontraron relés',
video: 'video' video: 'video',
'Show n new notes': 'Mostrar {{n}} nuevas notas'
} }
} }

View File

@@ -219,6 +219,7 @@ export default {
'Media upload service': 'Service de téléchargement de médias', 'Media upload service': 'Service de téléchargement de médias',
'Choose a relay': 'Choisir un relais', 'Choose a relay': 'Choisir un relais',
'no relays found': 'aucun relais trouvé', 'no relays found': 'aucun relais trouvé',
video: 'vidéo' video: 'vidéo',
'Show n new notes': 'Afficher {{n}} nouvelles notes'
} }
} }

View File

@@ -219,6 +219,7 @@ export default {
'Media upload service': 'Servizio di caricamento media', 'Media upload service': 'Servizio di caricamento media',
'Choose a relay': 'Scegli un relay', 'Choose a relay': 'Scegli un relay',
'no relays found': 'Nessun relay trovato', 'no relays found': 'Nessun relay trovato',
video: 'video' video: 'video',
'Show n new notes': 'Mostra {{n}} nuove note'
} }
} }

View File

@@ -217,6 +217,7 @@ export default {
'Media upload service': 'メディアアップロードサービス', 'Media upload service': 'メディアアップロードサービス',
'Choose a relay': 'リレイを選択', 'Choose a relay': 'リレイを選択',
'no relays found': 'リレイが見つかりません', 'no relays found': 'リレイが見つかりません',
video: 'ビデオ' video: 'ビデオ',
'Show n new notes': '新しいノートを{{n}}件表示'
} }
} }

View File

@@ -218,6 +218,7 @@ export default {
'Media upload service': 'Usługa przesyłania mediów', 'Media upload service': 'Usługa przesyłania mediów',
'Choose a relay': 'Wybierz transmiter', 'Choose a relay': 'Wybierz transmiter',
'no relays found': 'Nie znaleziono transmiterów', 'no relays found': 'Nie znaleziono transmiterów',
video: 'wideo' video: 'wideo',
'Show n new notes': 'Pokaż {{n}} nowych wpisów'
} }
} }

View File

@@ -218,6 +218,7 @@ export default {
'Media upload service': 'Serviço de upload de mídia', 'Media upload service': 'Serviço de upload de mídia',
'Choose a relay': 'Escolher um relé', 'Choose a relay': 'Escolher um relé',
'no relays found': 'nenhum relé encontrado', 'no relays found': 'nenhum relé encontrado',
video: 'vídeo' video: 'vídeo',
'Show n new notes': 'Mostrar {{n}} novas notas'
} }
} }

View File

@@ -219,6 +219,7 @@ export default {
'Media upload service': 'Serviço de Upload de Mídia', 'Media upload service': 'Serviço de Upload de Mídia',
'Choose a relay': 'Escolher um Relé', 'Choose a relay': 'Escolher um Relé',
'no relays found': 'nenhum relé encontrado', 'no relays found': 'nenhum relé encontrado',
video: 'vídeo' video: 'vídeo',
'Show n new notes': 'Mostrar {{n}} novas notas'
} }
} }

View File

@@ -220,6 +220,7 @@ export default {
'Media upload service': 'Служба загрузки медиафайлов', 'Media upload service': 'Служба загрузки медиафайлов',
'Choose a relay': 'Выберите ретранслятор', 'Choose a relay': 'Выберите ретранслятор',
'no relays found': 'ретрансляторы не найдены', 'no relays found': 'ретрансляторы не найдены',
video: 'видео' video: 'видео',
'Show n new notes': 'Показать {{n}} новых заметок'
} }
} }

View File

@@ -217,6 +217,7 @@ export default {
'Media upload service': '媒体上传服务', 'Media upload service': '媒体上传服务',
'Choose a relay': '选择一个服务器', 'Choose a relay': '选择一个服务器',
'no relays found': '未找到服务器', 'no relays found': '未找到服务器',
video: '视频' video: '视频',
'Show n new notes': '显示 {{n}} 条新笔记'
} }
} }

View File

@@ -56,6 +56,7 @@
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%; --popover-foreground: 240 10% 3.9%;
--primary: 259 43% 56%; --primary: 259 43% 56%;
--primary-hover: 259 43% 65%;
--primary-foreground: 0 0% 98%; --primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%; --secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%; --secondary-foreground: 240 5.9% 10%;
@@ -83,6 +84,7 @@
--popover: 240 10% 3.9%; --popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%; --popover-foreground: 0 0% 98%;
--primary: 259 43% 56%; --primary: 259 43% 56%;
--primary-hover: 259 43% 65%;
--primary-foreground: 240 5.9% 10%; --primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%; --secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%; --secondary-foreground: 0 0% 98%;

View File

@@ -22,7 +22,8 @@ export default {
}, },
primary: { primary: {
DEFAULT: 'hsl(var(--primary))', DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))' foreground: 'hsl(var(--primary-foreground))',
hover: 'hsl(var(--primary-hover))',
}, },
secondary: { secondary: {
DEFAULT: 'hsl(var(--secondary))', DEFAULT: 'hsl(var(--secondary))',