@@ -49,6 +49,9 @@ export type PostData = {
4949 * key so a repost is distinct from the original post. */
5050 repostId ?: string ;
5151
52+ /** Our estimation for how many replies this post has. */
53+ replyCount ?: number ;
54+
5255 signedEvent : v2 . SignedEvent ;
5356} ;
5457
@@ -121,6 +124,10 @@ export function decodeBundle<K extends ContentKind>(
121124 return { event, content, signedEvent : bundle . signedEvent } ;
122125}
123126
127+ export function eventKeyId ( eventKey : v2 . EventKey ) : string {
128+ return bytesToHex ( v2 . EventKey . toBinary ( eventKey ) ) ;
129+ }
130+
124131/** Decode a v2 EventBundle into PostData, or null if not a post. */
125132export function decodePostBundle ( bundle : v2 . EventBundle ) : PostData | null {
126133 const decoded = decodeBundle ( bundle , 'post' ) ;
@@ -130,20 +137,16 @@ export function decodePostBundle(bundle: v2.EventBundle): PostData | null {
130137 const key = event . key ;
131138 if ( ! key ?. signedBy ?. key ) return null ;
132139
133- const id = bytesToHex ( v2 . EventKey . toBinary ( key ) ) ;
140+ const id = eventKeyId ( key ) ;
134141 const reply = post . reply
135142 ? {
136- rootId : post . reply . root
137- ? bytesToHex ( v2 . EventKey . toBinary ( post . reply . root ) )
138- : undefined ,
143+ rootId : post . reply . root ? eventKeyId ( post . reply . root ) : undefined ,
139144 parentId : post . reply . parent
140- ? bytesToHex ( v2 . EventKey . toBinary ( post . reply . parent ) )
145+ ? eventKeyId ( post . reply . parent )
141146 : undefined ,
142147 }
143148 : undefined ;
144- const quoteId = post . quote
145- ? bytesToHex ( v2 . EventKey . toBinary ( post . quote ) )
146- : undefined ;
149+ const quoteId = post . quote ? eventKeyId ( post . quote ) : undefined ;
147150
148151 return {
149152 id,
@@ -156,6 +159,7 @@ export function decodePostBundle(bundle: v2.EventBundle): PostData | null {
156159 links : post . links ,
157160 reply,
158161 quoteId,
162+ replyCount : bundle . meta ?. replyCount ,
159163 signedEvent : v2 . SignedEvent . create ( {
160164 eventBytes : signedEvent . eventBytes ,
161165 signature : signedEvent . signature ,
@@ -183,8 +187,8 @@ function decodeRepostBundle(bundle: v2.EventBundle): {
183187 if ( ! target ) return null ;
184188 return {
185189 repostedBy : key . identity ,
186- targetId : bytesToHex ( v2 . EventKey . toBinary ( target ) ) ,
187- repostId : bytesToHex ( v2 . EventKey . toBinary ( key ) ) ,
190+ targetId : eventKeyId ( target ) ,
191+ repostId : eventKeyId ( key ) ,
188192 } ;
189193 } catch {
190194 return null ;
@@ -196,13 +200,6 @@ function decodeRepostBundle(bundle: v2.EventBundle): {
196200 * directly; reposts resolve their target post from `event_hints` (the
197201 * server ships the reposted post alongside) and surface it tagged with
198202 * `repostedBy`. A repost whose target isn't in the hints is dropped.
199- *
200- * Items are de-duplicated by their list key (`repostId ?? id`). The explore
201- * feed fans out across multiple servers and paginates with a single forward
202- * token, so the same post routinely arrives more than once. Without this,
203- * duplicate keys reach FlashList and recycled cells flash / the scroll
204- * position jumps while paginating. The first occurrence wins so already-
205- * rendered rows keep their position.
206203 */
207204export function decodeFeedItems ( response : v2 . GetFeedResponse ) : PostData [ ] {
208205 const hintPosts = new Map < string , PostData > ( ) ;
@@ -213,25 +210,17 @@ export function decodeFeedItems(response: v2.GetFeedResponse): PostData[] {
213210 }
214211
215212 const items : PostData [ ] = [ ] ;
216- const seen = new Set < string > ( ) ;
217- const pushUnique = ( item : PostData ) => {
218- const key = item . repostId ?? item . id ;
219- if ( seen . has ( key ) ) return ;
220- seen . add ( key ) ;
221- items . push ( item ) ;
222- } ;
223-
224213 for ( const bundle of response . eventBundles ) {
225214 const post = decodePostBundle ( bundle ) ;
226215 if ( post ) {
227- pushUnique ( post ) ;
216+ items . push ( post ) ;
228217 continue ;
229218 }
230219 const repost = decodeRepostBundle ( bundle ) ;
231220 if ( repost ) {
232221 const target = hintPosts . get ( repost . targetId ) ;
233222 if ( target ) {
234- pushUnique ( {
223+ items . push ( {
235224 ...target ,
236225 repostedBy : repost . repostedBy ,
237226 repostId : repost . repostId ,
@@ -242,19 +231,6 @@ export function decodeFeedItems(response: v2.GetFeedResponse): PostData[] {
242231 return items ;
243232}
244233
245- /** Extract the items and next page indicator from the feed response. */
246- export function decodeFeedQueryResult (
247- data : ArrayBuffer | undefined ,
248- ) : [ PostData [ ] , boolean ] {
249- if ( ! data ) {
250- return [ [ ] , false ] ;
251- }
252-
253- const response = v2 . GetFeedResponse . fromBinary ( new Uint8Array ( data ) ) ;
254- const hasNext = ! ! response . pageInfo ?. hasNextPage ;
255- return [ decodeFeedItems ( response ) , hasNext ] ;
256- }
257-
258234/** Get the forward cursor token from a previous feed query result */
259235export function extractFeedToken (
260236 _status : QueryStatus | undefined ,
@@ -333,7 +309,7 @@ export function bundleEventId(bundle: v2.EventBundle): string | null {
333309 try {
334310 const event = v2 . Event . fromBinary ( bundle . signedEvent . eventBytes ) ;
335311 if ( ! event . key ) return null ;
336- return bytesToHex ( v2 . EventKey . toBinary ( event . key ) ) ;
312+ return eventKeyId ( event . key ) ;
337313 } catch {
338314 return null ;
339315 }
0 commit comments