Browse Source

bug-fixes

imwald
Silberengel 1 month ago
parent
commit
08b104cd5f
  1. 57
      src/components/NoteStats/TopZaps.tsx
  2. 3
      src/components/NoteStats/index.tsx
  3. 5
      src/components/ReplyNoteList/index.tsx
  4. 61
      src/services/note-stats.service.ts

57
src/components/NoteStats/TopZaps.tsx

@ -1,57 +0,0 @@ @@ -1,57 +0,0 @@
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
import { useNoteStatsById } from '@/hooks/useNoteStatsById'
import { formatAmount } from '@/lib/lightning'
import { Zap } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useMemo, useState } from 'react'
import { SimpleUserAvatar } from '../UserAvatar'
import ZapDialog from '../ZapDialog'
export default function TopZaps({ event }: { event: Event }) {
const noteStats = useNoteStatsById(event.id)
const [zapIndex, setZapIndex] = useState(-1)
const topZaps = useMemo(() => {
return noteStats?.zaps?.sort((a, b) => b.amount - a.amount).slice(0, 10) || []
}, [noteStats])
if (!topZaps.length) return null
return (
<ScrollArea className="pb-2 mb-1">
<div className="flex gap-1">
{topZaps.map((zap, index) => (
<div
key={zap.pr}
className="flex gap-1 py-1 pl-1 pr-2 text-sm max-w-72 rounded-full bg-muted/80 items-center text-yellow-400 border border-yellow-400 hover:bg-yellow-400/20 cursor-pointer"
onClick={(e) => {
e.stopPropagation()
setZapIndex(index)
}}
>
<SimpleUserAvatar userId={zap.pubkey} size="xSmall" />
<Zap className="size-3 fill-yellow-400 shrink-0" />
<div className="font-semibold">{formatAmount(zap.amount)}</div>
<div className="truncate">{zap.comment}</div>
<div onClick={(e) => e.stopPropagation()}>
<ZapDialog
open={zapIndex === index}
setOpen={(open) => {
if (open) {
setZapIndex(index)
} else {
setZapIndex(-1)
}
}}
pubkey={event.pubkey}
event={event}
defaultAmount={zap.amount}
defaultComment={zap.comment}
/>
</div>
</div>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
)
}

3
src/components/NoteStats/index.tsx

@ -15,7 +15,6 @@ import Likes from './Likes' @@ -15,7 +15,6 @@ import Likes from './Likes'
import ReplyButton from './ReplyButton'
import RepostButton from './RepostButton'
import SeenOnButton from './SeenOnButton'
import TopZaps from './TopZaps'
import ZapButton from './ZapButton'
export default function NoteStats({
@ -76,7 +75,6 @@ export default function NoteStats({ @@ -76,7 +75,6 @@ export default function NoteStats({
<div className={cn('select-none', className)} data-note-stats onClick={(e) => e.stopPropagation()}>
{displayTopZapsAndLikes && (
<>
{!isRssArticleRoot && <TopZaps event={event} />}
{/* Kind 11: LikeButton already shows ⬆/⬇; Likes row would duplicate those pills */}
{!isDiscussion && !isRssArticleRoot && <Likes event={event} />}
</>
@ -105,7 +103,6 @@ export default function NoteStats({ @@ -105,7 +103,6 @@ export default function NoteStats({
<div className={cn('select-none', className)} data-note-stats onClick={(e) => e.stopPropagation()}>
{displayTopZapsAndLikes && (
<>
{!isRssArticleRoot && <TopZaps event={event} />}
{!isDiscussion && !isRssArticleRoot && <Likes event={event} />}
</>
)}

5
src/components/ReplyNoteList/index.tsx

@ -230,9 +230,10 @@ function ReplyNoteList({ @@ -230,9 +230,10 @@ function ReplyNoteList({
const zapsForFeed = useMemo(() => {
if (shouldHideInteractions(event)) return []
const raw = noteStats?.zaps ?? []
const nonZero = raw.filter((z) => z.amount > 0) // Suppress 0 sat zaps (spam)
const filtered =
isTrustLoaded && hideUntrustedInteractions ? raw.filter((z) => isUserTrusted(z.pubkey)) : raw
return [...filtered].sort((a, b) => b.amount - a.amount)
isTrustLoaded && hideUntrustedInteractions ? nonZero.filter((z) => isUserTrusted(z.pubkey)) : nonZero
return [...filtered].sort((a, b) => b.amount - a.amount) // Largest to smallest
}, [event, noteStats, isTrustLoaded, hideUntrustedInteractions, isUserTrusted])
const [timelineKey] = useState<string | undefined>(undefined)

61
src/services/note-stats.service.ts

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
import { ExtendedKind, FAST_READ_RELAY_URLS } from '@/constants'
import { E_TAG_FILTER_BLOCKED_RELAY_URLS, ExtendedKind, SEARCHABLE_RELAY_URLS } from '@/constants'
import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event'
import { getZapInfoFromEvent } from '@/lib/event-metadata'
import logger from '@/lib/logger'
import { getEmojiInfosFromEmojiTags, getFirstHexEventIdFromETags, tagNameEquals } from '@/lib/tag'
import { normalizeUrl } from '@/lib/url'
import { eventService } from '@/services/client.service'
import client, { eventService } from '@/services/client.service'
import { TEmoji } from '@/types'
import dayjs from 'dayjs'
import { Event, Filter, kinds } from 'nostr-tools'
@ -113,8 +113,7 @@ class NoteStatsService { @@ -113,8 +113,7 @@ class NoteStatsService {
since = oldStats.updatedAt
}
// Use optimized relay selection - fewer relays, better performance
const finalRelayUrls = this.getOptimizedRelayList()
const finalRelayUrls = await this.buildNoteStatsRelayList(event)
const replaceableCoordinate = isReplaceableEvent(event.kind)
? getReplaceableCoordinateFromEvent(event)
@ -145,14 +144,41 @@ class NoteStatsService { @@ -145,14 +144,41 @@ class NoteStatsService {
}
}
private getOptimizedRelayList(): string[] {
// Use only FAST_READ_RELAY_URLS for optimal performance
const normalizedRelays = FAST_READ_RELAY_URLS
.map(url => normalizeUrl(url))
.filter((url): url is string => !!url)
.slice(0, 2) // Limit to 2 relays for better performance and reduced load
return Array.from(new Set(normalizedRelays))
/**
* Build relay list for note stats: search relays + relay(s) event was seen on + author's inboxes, deduplicated.
* Excludes E_TAG_FILTER_BLOCKED_RELAY_URLS (stats use #e filters).
*/
private async buildNoteStatsRelayList(event: Event): Promise<string[]> {
const blocked = new Set(
E_TAG_FILTER_BLOCKED_RELAY_URLS.map((u) => (normalizeUrl(u) || u).toLowerCase()).filter(Boolean)
)
const seen = new Set<string>()
const add = (url: string | undefined) => {
if (!url) return
const n = normalizeUrl(url)
if (!n || blocked.has(n.toLowerCase()) || seen.has(n)) return
seen.add(n)
}
// 1. Search relays
SEARCHABLE_RELAY_URLS.forEach(add)
// 2. Relay(s) where the event was seen
client.getSeenEventRelayUrls(event.id).forEach(add)
// 3. Author's inboxes (read relays from kind 10002)
try {
const relayList = await Promise.race([
client.fetchRelayList(event.pubkey),
new Promise<{ read?: string[] }>((r) => setTimeout(() => r({}), 2000))
])
;(relayList?.read ?? []).slice(0, 10).forEach(add)
} catch {
// ignore
}
return Array.from(seen)
}
private buildFilters(event: Event, replaceableCoordinate?: string, since?: number): Filter[] {
@ -162,6 +188,11 @@ class NoteStatsService { @@ -162,6 +188,11 @@ class NoteStatsService {
kinds: [kinds.Reaction, kinds.Repost, kinds.ShortTextNote, ExtendedKind.COMMENT, ExtendedKind.VOICE_COMMENT, kinds.Highlights],
limit: 50 // Reduced limit for better performance
},
{
'#e': [event.id],
kinds: [kinds.Zap],
limit: 100
},
{
'#q': [event.id],
kinds: [kinds.ShortTextNote, ExtendedKind.COMMENT, ExtendedKind.VOICE_COMMENT],
@ -176,6 +207,11 @@ class NoteStatsService { @@ -176,6 +207,11 @@ class NoteStatsService {
kinds: [kinds.Reaction, kinds.Repost, kinds.ShortTextNote, ExtendedKind.COMMENT, ExtendedKind.VOICE_COMMENT, kinds.Highlights],
limit: 50
},
{
'#a': [replaceableCoordinate],
kinds: [kinds.Zap],
limit: 100
},
{
'#q': [replaceableCoordinate],
kinds: [kinds.ShortTextNote, ExtendedKind.COMMENT, ExtendedKind.VOICE_COMMENT],
@ -379,6 +415,7 @@ class NoteStatsService { @@ -379,6 +415,7 @@ class NoteStatsService {
if (!info) return
const { originalEventId, senderPubkey, invoice, amount, comment } = info
if (!originalEventId || !senderPubkey) return
if (!amount || amount <= 0) return // Suppress 0 sat zaps (spam)
if (originalEventAuthor && originalEventAuthor === senderPubkey) {
return

Loading…
Cancel
Save