feat: support for generic repost

This commit is contained in:
codytseng 2025-11-20 13:34:05 +08:00
parent 14b3fbd496
commit a40d7b0676
11 changed files with 92 additions and 68 deletions

View file

@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next'
const KIND_FILTER_OPTIONS = [
{ kindGroup: [kinds.ShortTextNote, ExtendedKind.COMMENT], label: 'Posts' },
{ kindGroup: [kinds.Repost], label: 'Reposts' },
{ kindGroup: [kinds.Repost, kinds.GenericRepost], label: 'Reposts' },
{ kindGroup: [kinds.LongFormArticle], label: 'Articles' },
{ kindGroup: [kinds.Highlights], label: 'Highlights' },
{ kindGroup: [ExtendedKind.POLL], label: 'Polls' },

View file

@ -11,12 +11,14 @@ export default function RepostNoteCard({
event,
className,
filterMutedNotes = true,
pinned = false
pinned = false,
reposters
}: {
event: Event
className?: string
filterMutedNotes?: boolean
pinned?: boolean
reposters?: string[]
}) {
const { mutePubkeySet } = useMuteList()
const { hideContentMentioningMutedUsers } = useContentPolicy()
@ -42,7 +44,10 @@ export default function RepostNoteCard({
}
}
if (eventFromContent && verifyEvent(eventFromContent)) {
if (eventFromContent.kind === kinds.Repost) {
if (
eventFromContent.kind === kinds.Repost ||
eventFromContent.kind === kinds.GenericRepost
) {
return
}
client.addEventToCache(eventFromContent)
@ -84,7 +89,7 @@ export default function RepostNoteCard({
return (
<MainNoteCard
className={className}
reposters={[event.pubkey]}
reposters={reposters?.includes(event.pubkey) ? reposters : [event.pubkey]}
event={targetEvent}
pinned={pinned}
/>

View file

@ -34,13 +34,14 @@ export default function NoteCard({
}, [event, filterMutedNotes, mutePubkeySet])
if (shouldHide) return null
if (event.kind === kinds.Repost) {
if (event.kind === kinds.Repost || event.kind === kinds.GenericRepost) {
return (
<RepostNoteCard
event={event}
className={className}
filterMutedNotes={filterMutedNotes}
pinned={pinned}
reposters={reposters}
/>
)
}

View file

@ -11,7 +11,7 @@ import { useUserTrust } from '@/providers/UserTrustProvider'
import client from '@/services/client.service'
import { TFeedSubRequest } from '@/types'
import dayjs from 'dayjs'
import { Event, kinds, verifyEvent } from 'nostr-tools'
import { Event, kinds } from 'nostr-tools'
import { decode } from 'nostr-tools/nip19'
import {
forwardRef,
@ -117,84 +117,75 @@ const NoteList = forwardRef(
const repostersMap = new Map<string, Set<string>>()
// Final list of filtered events
const filteredEvents: Event[] = []
const keys: string[] = []
events.slice(0, showCount).forEach((evt) => {
events.forEach((evt) => {
const key = getEventKey(evt)
if (keySet.has(key)) return
keySet.add(key)
if (shouldHideEvent(evt)) return
if (hideReplies && isReplyNoteEvent(evt)) return
if (evt.kind !== kinds.Repost) {
if (evt.kind !== kinds.Repost && evt.kind !== kinds.GenericRepost) {
filteredEvents.push(evt)
keys.push(key)
return
}
let targetEventKey: string | undefined
let eventFromContent: Event | null = null
if (evt.content) {
try {
eventFromContent = JSON.parse(evt.content) as Event
} catch {
eventFromContent = null
const targetTag = evt.tags.find(tagNameEquals('a')) ?? evt.tags.find(tagNameEquals('e'))
if (targetTag) {
targetEventKey = getKeyFromTag(targetTag)
} else {
// Attempt to extract the target event from the repost content
if (evt.content) {
try {
eventFromContent = JSON.parse(evt.content) as Event
} catch {
eventFromContent = null
}
}
if (eventFromContent) {
if (
eventFromContent.kind === kinds.Repost ||
eventFromContent.kind === kinds.GenericRepost
) {
return
}
if (shouldHideEvent(evt)) return
targetEventKey = getEventKey(eventFromContent)
}
}
if (eventFromContent && verifyEvent(eventFromContent)) {
if (eventFromContent.kind === kinds.Repost) {
return
}
if (shouldHideEvent(eventFromContent)) return
client.addEventToCache(eventFromContent)
const targetSeenOn = client.getSeenEventRelays(eventFromContent.id)
if (targetSeenOn.length === 0) {
const seenOn = client.getSeenEventRelays(evt.id)
seenOn.forEach((relay) => {
client.trackEventSeenOn(eventFromContent.id, relay)
})
}
const targetEventKey = getEventKey(eventFromContent)
if (targetEventKey) {
// Add to reposters map
const reposters = repostersMap.get(targetEventKey)
if (reposters) {
reposters.add(evt.pubkey)
} else {
repostersMap.set(targetEventKey, new Set([evt.pubkey]))
}
// If the target event is not already included, add it now
if (!keySet.has(targetEventKey)) {
filteredEvents.push(eventFromContent)
filteredEvents.push(evt)
keys.push(targetEventKey)
keySet.add(targetEventKey)
}
return
}
const targetTag = evt.tags.find(tagNameEquals('a')) ?? evt.tags.find(tagNameEquals('e'))
if (targetTag) {
const targetEventKey = getKeyFromTag(targetTag)
if (targetEventKey) {
// Add to reposters map
const reposters = repostersMap.get(targetEventKey)
if (reposters) {
reposters.add(evt.pubkey)
} else {
repostersMap.set(targetEventKey, new Set([evt.pubkey]))
}
// If the target event is already included, skip adding this repost
if (keySet.has(targetEventKey)) {
return
}
}
}
// If we can't find the original event, just show the repost itself
filteredEvents.push(evt)
return
})
return filteredEvents.map((evt) => {
const key = getEventKey(evt)
return filteredEvents.map((evt, i) => {
const key = keys[i]
return { key, event: evt, reposters: Array.from(repostersMap.get(key) ?? []) }
})
}, [events, showCount, shouldHideEvent, hideReplies])
}, [events, shouldHideEvent, hideReplies])
const slicedNotes = useMemo(() => {
return filteredNotes.slice(0, showCount)
}, [filteredNotes, showCount])
const filteredNewEvents = useMemo(() => {
const keySet = new Set<string>()
@ -369,7 +360,7 @@ const NoteList = forwardRef(
const list = (
<div className="min-h-screen">
{pinnedEventIds?.map((id) => <PinnedNoteCard key={id} eventId={id} className="w-full" />)}
{filteredNotes.map(({ key, event, reposters }) => (
{slicedNotes.map(({ key, event, reposters }) => (
<NoteCard
key={key}
className="w-full"

View file

@ -51,7 +51,7 @@ export function NotificationItem({
) {
return <MentionNotification notification={notification} isNew={isNew} />
}
if (notification.kind === kinds.Repost) {
if (notification.kind === kinds.Repost || notification.kind === kinds.GenericRepost) {
return <RepostNotification notification={notification} isNew={isNew} />
}
if (notification.kind === kinds.Zap) {

View file

@ -58,13 +58,14 @@ const NotificationList = forwardRef((_, ref) => {
ExtendedKind.POLL
]
case 'reactions':
return [kinds.Reaction, kinds.Repost, ExtendedKind.POLL_RESPONSE]
return [kinds.Reaction, kinds.Repost, kinds.GenericRepost, ExtendedKind.POLL_RESPONSE]
case 'zaps':
return [kinds.Zap]
default:
return [
kinds.ShortTextNote,
kinds.Repost,
kinds.GenericRepost,
kinds.Reaction,
kinds.Zap,
ExtendedKind.COMMENT,

View file

@ -1,5 +1,6 @@
import { useSecondaryPage } from '@/PageManager'
import { useStuffStatsById } from '@/hooks/useStuffStatsById'
import { getEventKey } from '@/lib/event'
import { toProfile } from '@/lib/link'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { useUserTrust } from '@/providers/UserTrustProvider'
@ -19,7 +20,7 @@ export default function RepostList({ event }: { event: Event }) {
const { push } = useSecondaryPage()
const { isSmallScreen } = useScreenSize()
const { hideUntrustedInteractions, isUserTrusted } = useUserTrust()
const noteStats = useStuffStatsById(event.id)
const noteStats = useStuffStatsById(getEventKey(event))
const filteredReposts = useMemo(() => {
return (noteStats?.reposts ?? [])
.filter((repost) => !hideUntrustedInteractions || isUserTrusted(repost.pubkey))