diff --git a/packages/app/src/Feed/ThreadFeed.ts b/packages/app/src/Feed/ThreadFeed.ts index 8fe289a2..5e3a6468 100644 --- a/packages/app/src/Feed/ThreadFeed.ts +++ b/packages/app/src/Feed/ThreadFeed.ts @@ -1,101 +1,48 @@ import { useEffect, useMemo, useState } from "react"; -import { u256, EventKind, NostrLink, FlatNoteStore, RequestBuilder, NostrPrefix } from "@snort/system"; +import { EventKind, NostrLink, RequestBuilder, NoteCollection } from "@snort/system"; import { useRequestBuilder } from "@snort/system-react"; -import { appendDedupe } from "SnortUtils"; import useLogin from "Hooks/useLogin"; +import { useReactions } from "./FeedReactions"; -interface RelayTaggedEventId { - id: u256; - relay?: string; -} export default function useThreadFeed(link: NostrLink) { - const [trackingEvents, setTrackingEvent] = useState>([]); - const [trackingATags, setTrackingATags] = useState([]); - const [allEvents, setAllEvents] = useState>([]); + const [allEvents, setAllEvents] = useState>([]); const pref = useLogin().preferences; const sub = useMemo(() => { - const sub = new RequestBuilder(`thread:${link.id.substring(0, 8)}`); + const sub = new RequestBuilder(`thread:${link.id}`); sub.withOptions({ leaveOpen: true, }); - if (trackingEvents.length > 0) { - for (const te of trackingEvents) { - const fTracking = sub.withFilter(); - fTracking.ids([te.id]); - if (te.relay) { - fTracking.relay(te.relay); - } - } - } + sub.withFilter() + .kinds([EventKind.TextNote]) + .link(link); if (allEvents.length > 0) { - sub + const f = sub .withFilter() - .kinds( - pref.enableReactions - ? [EventKind.Reaction, EventKind.TextNote, EventKind.Repost, EventKind.ZapReceipt] - : [EventKind.TextNote, EventKind.ZapReceipt, EventKind.Repost], - ) - .tag( - "e", - allEvents.map(a => a.id), - ); - } - if (trackingATags.length > 0) { - const parsed = trackingATags.map(a => a.split(":")); - sub - .withFilter() - .kinds(parsed.map(a => Number(a[0]))) - .authors(parsed.map(a => a[1])) - .tag( - "d", - parsed.map(a => a[2]), - ); - sub.withFilter().tag("a", trackingATags); + .kinds([EventKind.TextNote]); + allEvents.forEach(x => f.replyToLink(x)); } return sub; - }, [trackingEvents, trackingATags, allEvents, pref]); + }, [allEvents.length, pref]); - const store = useRequestBuilder(FlatNoteStore, sub); - - useEffect(() => { - if (link.type === NostrPrefix.Address) { - setTrackingATags([`${link.kind}:${link.author}:${link.id}`]); - } else { - const lnk = { - id: link.id, - relay: link.relays?.[0], - }; - setTrackingEvent([lnk]); - setAllEvents([lnk]); - } - }, [link.id]); + const store = useRequestBuilder(NoteCollection, sub); useEffect(() => { if (store.data) { const mainNotes = store.data?.filter(a => a.kind === EventKind.TextNote || a.kind === EventKind.Polls) ?? []; - - const eTags = mainNotes - .map(a => - a.tags - .filter(b => b[0] === "e") - .map(b => { - return { - id: b[1], - relay: b[2], - }; - }), - ) - .flat(); - const eTagsMissing = eTags.filter(a => !mainNotes.some(b => b.id === a.id)); - setTrackingEvent(s => appendDedupe(s, eTagsMissing)); - setAllEvents(s => appendDedupe(s, eTags)); - - const aTags = mainNotes.map(a => a.tags.filter(b => b[0] === "a").map(b => b[1])).flat(); - setTrackingATags(s => appendDedupe(s, aTags)); + const links = mainNotes.map(a => [ + NostrLink.fromEvent(a), + ...a.tags.filter(a => a[0] === "e" || a[0] === "a").map(v => NostrLink.fromTag(v)) + ]).flat(); + setAllEvents(links); } - }, [store]); + }, [store.data?.length]); - return store; + const reactions = useReactions(`thread:${link.id}:reactions`, allEvents); + + return { + thread: store.data ?? [], + reactions: reactions.data ?? [], + }; } diff --git a/packages/app/src/Hooks/useThreadContext.tsx b/packages/app/src/Hooks/useThreadContext.tsx index 0d11130d..ba6171b7 100644 --- a/packages/app/src/Hooks/useThreadContext.tsx +++ b/packages/app/src/Hooks/useThreadContext.tsx @@ -25,19 +25,19 @@ export const ThreadContext = createContext({} as ThreadContext); export function ThreadContextWrapper({ link, children }: { link: NostrLink; children?: ReactNode }) { const location = useLocation(); const [currentId, setCurrentId] = useState(link.id); - const thread = useThreadFeed(link); + const feed = useThreadFeed(link); const chains = useMemo(() => { const chains = new Map>(); - if (thread.data) { - thread.data + if (feed.thread) { + feed.thread ?.sort((a, b) => b.created_at - a.created_at) .forEach(v => { const t = EventExt.extractThread(v); let replyTo = t?.replyTo?.value ?? t?.root?.value; if (t?.root?.key === "a" && t?.root?.value) { const parsed = t.root.value.split(":"); - replyTo = thread.data?.find( + replyTo = feed.thread?.find( a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2], )?.id; } @@ -51,12 +51,12 @@ export function ThreadContextWrapper({ link, children }: { link: NostrLink; chil }); } return chains; - }, [thread.data]); + }, [feed.thread]); // Root is the parent of the current note or the current note if its a root note or the root of the thread const root = useMemo(() => { const currentNote = - thread.data?.find( + feed.thread?.find( ne => ne.id === currentId || (link.type === NostrPrefix.Address && findTag(ne, "d") === currentId && ne.pubkey === link.author), @@ -74,16 +74,16 @@ export function ThreadContextWrapper({ link, children }: { link: NostrLink; chil if (replyTo) { if (replyTo.key === "a" && replyTo.value) { const parsed = replyTo.value.split(":"); - return thread.data?.find( + return feed.thread?.find( a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2], ); } if (replyTo.value) { - return thread.data?.find(a => a.id === replyTo.value); + return feed.thread?.find(a => a.id === replyTo.value); } } - const possibleRoots = thread.data?.filter(a => { + const possibleRoots = feed.thread?.filter(a => { const thread = EventExt.extractThread(a); return isRoot(thread); }); @@ -98,14 +98,14 @@ export function ThreadContextWrapper({ link, children }: { link: NostrLink; chil } } } - }, [thread.data, currentId, location]); + }, [feed.thread, currentId, location]); const ctxValue = useMemo(() => { return { current: currentId, root, chains, - data: thread.data, + data: feed.reactions, setCurrent: v => setCurrentId(v), } as ThreadContext; }, [root, chains]);