diff --git a/src/components/KindFilter/index.tsx b/src/components/KindFilter/index.tsx index ebf4e593..4e9ae39c 100644 --- a/src/components/KindFilter/index.tsx +++ b/src/components/KindFilter/index.tsx @@ -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' }, diff --git a/src/components/NoteCard/RepostNoteCard.tsx b/src/components/NoteCard/RepostNoteCard.tsx index 6741fc9b..2979a6d3 100644 --- a/src/components/NoteCard/RepostNoteCard.tsx +++ b/src/components/NoteCard/RepostNoteCard.tsx @@ -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 ( diff --git a/src/components/NoteCard/index.tsx b/src/components/NoteCard/index.tsx index 05d4f0ab..f0db6aaf 100644 --- a/src/components/NoteCard/index.tsx +++ b/src/components/NoteCard/index.tsx @@ -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 ( ) } diff --git a/src/components/NoteList/index.tsx b/src/components/NoteList/index.tsx index 04be8a4f..b003b508 100644 --- a/src/components/NoteList/index.tsx +++ b/src/components/NoteList/index.tsx @@ -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>() // 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() @@ -369,7 +360,7 @@ const NoteList = forwardRef( const list = (
{pinnedEventIds?.map((id) => )} - {filteredNotes.map(({ key, event, reposters }) => ( + {slicedNotes.map(({ key, event, reposters }) => ( } - if (notification.kind === kinds.Repost) { + if (notification.kind === kinds.Repost || notification.kind === kinds.GenericRepost) { return } if (notification.kind === kinds.Zap) { diff --git a/src/components/NotificationList/index.tsx b/src/components/NotificationList/index.tsx index 9ddcaccc..f09f94e2 100644 --- a/src/components/NotificationList/index.tsx +++ b/src/components/NotificationList/index.tsx @@ -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, diff --git a/src/components/RepostList/index.tsx b/src/components/RepostList/index.tsx index 232c4807..e1c51704 100644 --- a/src/components/RepostList/index.tsx +++ b/src/components/RepostList/index.tsx @@ -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)) diff --git a/src/constants.ts b/src/constants.ts index 69168177..6067094a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -88,6 +88,7 @@ export const ExtendedKind = { export const SUPPORTED_KINDS = [ kinds.ShortTextNote, kinds.Repost, + kinds.GenericRepost, ExtendedKind.PICTURE, ExtendedKind.VIDEO, ExtendedKind.SHORT_VIDEO, diff --git a/src/lib/draft-event.ts b/src/lib/draft-event.ts index 6195ca77..022d5d84 100644 --- a/src/lib/draft-event.ts +++ b/src/lib/draft-event.ts @@ -118,12 +118,22 @@ export function createRepostDraftEvent(event: Event): TDraftEvent { const isProtected = isProtectedEvent(event) const tags = [buildETag(event.id, event.pubkey), buildPTag(event.pubkey)] + if (event.kind === kinds.ShortTextNote) { + return { + kind: kinds.Repost, + content: isProtected ? '' : JSON.stringify(event), + tags, + created_at: dayjs().unix() + } + } + + tags.push(buildKTag(event.kind)) if (isReplaceableEvent(event.kind)) { tags.push(buildATag(event)) } return { - kind: kinds.Repost, + kind: kinds.GenericRepost, content: isProtected ? '' : JSON.stringify(event), tags, created_at: dayjs().unix() diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts index 4d5967bc..0ba65151 100644 --- a/src/services/local-storage.service.ts +++ b/src/services/local-storage.service.ts @@ -23,6 +23,7 @@ import { TThemeSetting, TTranslationServiceConfig } from '@/types' +import { kinds } from 'nostr-tools' class LocalStorageService { static instance: LocalStorageService @@ -181,10 +182,13 @@ class LocalStorageService { showKindSet.delete(24236) // remove typo kind showKindSet.add(ExtendedKind.ADDRESSABLE_SHORT_VIDEO) } + if (showKindsVersion < 4 && showKindSet.has(kinds.Repost)) { + showKindSet.add(kinds.GenericRepost) + } this.showKinds = Array.from(showKindSet) } window.localStorage.setItem(StorageKey.SHOW_KINDS, JSON.stringify(this.showKinds)) - window.localStorage.setItem(StorageKey.SHOW_KINDS_VERSION, '3') + window.localStorage.setItem(StorageKey.SHOW_KINDS_VERSION, '4') this.hideContentMentioningMutedUsers = window.localStorage.getItem(StorageKey.HIDE_CONTENT_MENTIONING_MUTED_USERS) === 'true' diff --git a/src/services/stuff-stats.service.ts b/src/services/stuff-stats.service.ts index 9a057e26..b36c8fbc 100644 --- a/src/services/stuff-stats.service.ts +++ b/src/services/stuff-stats.service.ts @@ -107,7 +107,10 @@ class StuffStatsService { ? { '#e': [event.id], authors: [pubkey], - kinds: [kinds.Reaction, kinds.Repost] + kinds: + event.kind === kinds.ShortTextNote + ? [kinds.Reaction, kinds.Repost] + : [kinds.Reaction, kinds.Repost, kinds.GenericRepost] } : { '#i': [externalContent], @@ -120,7 +123,7 @@ class StuffStatsService { filters.push({ '#a': [replaceableCoordinate], authors: [pubkey], - kinds: [kinds.Reaction, kinds.Repost] + kinds: [kinds.Reaction, kinds.Repost, kinds.GenericRepost] }) } @@ -218,7 +221,7 @@ class StuffStatsService { targetKey = this.addLikeByEvent(evt) } else if (evt.kind === ExtendedKind.EXTERNAL_CONTENT_REACTION) { targetKey = this.addExternalContentLikeByEvent(evt) - } else if (evt.kind === kinds.Repost) { + } else if (evt.kind === kinds.Repost || evt.kind === kinds.GenericRepost) { targetKey = this.addRepostByEvent(evt) } else if (evt.kind === kinds.Zap) { targetKey = this.addZapByEvent(evt) @@ -291,18 +294,25 @@ class StuffStatsService { } private addRepostByEvent(evt: Event) { - const eventId = evt.tags.find(tagNameEquals('e'))?.[1] - if (!eventId) return + let targetEventKey + targetEventKey = evt.tags.find(tagNameEquals('a'))?.[1] + if (!targetEventKey) { + targetEventKey = evt.tags.find(tagNameEquals('e'))?.[1] + } - const old = this.stuffStatsMap.get(eventId) || {} + if (!targetEventKey) { + return + } + + const old = this.stuffStatsMap.get(targetEventKey) || {} const repostPubkeySet = old.repostPubkeySet || new Set() const reposts = old.reposts || [] if (repostPubkeySet.has(evt.pubkey)) return repostPubkeySet.add(evt.pubkey) reposts.push({ id: evt.id, pubkey: evt.pubkey, created_at: evt.created_at }) - this.stuffStatsMap.set(eventId, { ...old, repostPubkeySet, reposts }) - return eventId + this.stuffStatsMap.set(targetEventKey, { ...old, repostPubkeySet, reposts }) + return targetEventKey } private addZapByEvent(evt: Event) {