Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Refactor feed slices (#4834)

* Copy FeedViewPost into FeedSliceItem

* Explicitly construct feed slice items by copying known fields

* Type rootItem as FeedViewPost for now

Mergefeed logic relies on that.

* Unify reason and __source for slice items

* Move feedContext out of FeedSliceItem

* Remove slice.isFlattenedReply

* Remove unnused slice.ts

* Inline slice.isFullThread

* Refactor condition for clarity

* Extract slice.includesThreadRoot

* Encapsulate more usages of slice.rootItem into slice

* Rename slice.rootItem so semi-private slice._feedPost

* Move reason into slice

* Simplify slice ctor argument

* Reorder getters to reduce diff

* Make feedContext a getter to reduce diff

authored by

dan and committed by
GitHub
ac1538ba 39140252

+75 -69
+70 -61
src/lib/api/feed-manip.ts
··· 14 14 slices: FeedViewPostsSlice[], 15 15 ) => FeedViewPostsSlice[] 16 16 17 + type FeedSliceItem = { 18 + post: AppBskyFeedDefs.PostView 19 + reply?: AppBskyFeedDefs.ReplyRef 20 + } 21 + 22 + function toSliceItem(feedViewPost: FeedViewPost): FeedSliceItem { 23 + return { 24 + post: feedViewPost.post, 25 + reply: feedViewPost.reply, 26 + } 27 + } 28 + 17 29 export class FeedViewPostsSlice { 18 30 _reactKey: string 19 - isFlattenedReply = false 31 + _feedPost: FeedViewPost 32 + items: FeedSliceItem[] 20 33 21 - constructor(public items: FeedViewPost[]) { 22 - const item = items[0] 23 - this._reactKey = `slice-${item.post.uri}-${ 24 - item.reason?.indexedAt || item.post.indexedAt 34 + constructor(feedPost: FeedViewPost) { 35 + this._feedPost = feedPost 36 + this._reactKey = `slice-${feedPost.post.uri}-${ 37 + feedPost.reason?.indexedAt || feedPost.post.indexedAt 25 38 }` 39 + this.items = [toSliceItem(feedPost)] 26 40 } 27 41 28 42 get uri() { 29 - if (this.isFlattenedReply) { 30 - return this.items[1].post.uri 31 - } 32 - return this.items[0].post.uri 33 - } 34 - 35 - get ts() { 36 - if (this.items[0].reason?.indexedAt) { 37 - return this.items[0].reason.indexedAt as string 38 - } 39 - return this.items[0].post.indexedAt 43 + return this._feedPost.post.uri 40 44 } 41 45 42 46 get isThread() { ··· 48 52 ) 49 53 } 50 54 51 - get isFullThread() { 52 - return this.isThread && !this.items[0].reply 53 - } 54 - 55 - get rootItem() { 56 - if (this.isFlattenedReply) { 57 - return this.items[1] 58 - } 59 - return this.items[0] 55 + get isQuotePost() { 56 + const embed = this._feedPost.post.embed 57 + return ( 58 + AppBskyEmbedRecord.isView(embed) || 59 + AppBskyEmbedRecordWithMedia.isView(embed) 60 + ) 60 61 } 61 62 62 63 get isReply() { 63 64 return ( 64 - AppBskyFeedPost.isRecord(this.rootItem.post.record) && 65 - !!this.rootItem.post.record.reply 65 + AppBskyFeedPost.isRecord(this._feedPost.post.record) && 66 + !!this._feedPost.post.record.reply 66 67 ) 67 68 } 68 69 69 - get source(): ReasonFeedSource | undefined { 70 - return this.items.find(item => '__source' in item && !!item.__source) 71 - ?.__source as ReasonFeedSource 70 + get reason() { 71 + return '__source' in this._feedPost 72 + ? (this._feedPost.__source as ReasonFeedSource) 73 + : this._feedPost.reason 72 74 } 73 75 74 76 get feedContext() { 75 - return this.items.find(item => item.feedContext)?.feedContext 77 + return this._feedPost.feedContext 78 + } 79 + 80 + get isRepost() { 81 + const reason = this._feedPost.reason 82 + return AppBskyFeedDefs.isReasonRepost(reason) 83 + } 84 + 85 + get includesThreadRoot() { 86 + return !this.items[0].reply 87 + } 88 + 89 + get likeCount() { 90 + return this._feedPost.post.likeCount ?? 0 76 91 } 77 92 78 93 containsUri(uri: string) { ··· 97 112 if (this.items[0].reply) { 98 113 const reply = this.items[0].reply 99 114 if (AppBskyFeedDefs.isPostView(reply.parent)) { 100 - this.isFlattenedReply = true 101 115 this.items.splice(0, 0, {post: reply.parent}) 102 116 } 103 117 } 104 118 } 105 119 106 120 isFollowingAllAuthors(userDid: string) { 107 - const item = this.rootItem 108 - if (item.post.author.did === userDid) { 121 + const feedPost = this._feedPost 122 + if (feedPost.post.author.did === userDid) { 109 123 return true 110 124 } 111 - if (AppBskyFeedDefs.isPostView(item.reply?.parent)) { 112 - const parent = item.reply?.parent 125 + if (AppBskyFeedDefs.isPostView(feedPost.reply?.parent)) { 126 + const parent = feedPost.reply?.parent 113 127 if (parent?.author.did === userDid) { 114 128 return true 115 129 } 116 130 return ( 117 - parent?.author.viewer?.following && item.post.author.viewer?.following 131 + parent?.author.viewer?.following && 132 + feedPost.post.author.viewer?.following 118 133 ) 119 134 } 120 135 return false ··· 127 142 feed: FeedViewPost[], 128 143 _opts?: {dryRun: boolean; maintainOrder: boolean}, 129 144 ): FeedViewPostsSlice[] { 130 - return feed.map(item => new FeedViewPostsSlice([item])) 145 + return feed.map(item => new FeedViewPostsSlice(item)) 131 146 } 132 147 } 133 148 ··· 165 180 }) 166 181 167 182 if (maintainOrder) { 168 - slices = feed.map(item => new FeedViewPostsSlice([item])) 183 + slices = feed.map(item => new FeedViewPostsSlice(item)) 169 184 } else { 170 185 // arrange the posts into thread slices 171 186 for (let i = feed.length - 1; i >= 0; i--) { ··· 192 207 } 193 208 } 194 209 195 - slices.unshift(new FeedViewPostsSlice([item])) 210 + slices.unshift(new FeedViewPostsSlice(item)) 196 211 } 197 212 } 198 213 ··· 215 230 216 231 // turn non-threads with reply parents into threads 217 232 for (const slice of slices) { 218 - if (!slice.isThread && !slice.items[0].reason && slice.items[0].reply) { 233 + if (!slice.isThread && !slice.reason && slice.items[0].reply) { 219 234 const reply = slice.items[0].reply 220 235 if ( 221 236 AppBskyFeedDefs.isPostView(reply.parent) && ··· 256 271 257 272 static removeReposts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) { 258 273 for (let i = slices.length - 1; i >= 0; i--) { 259 - const reason = slices[i].rootItem.reason 260 - if (AppBskyFeedDefs.isReasonRepost(reason)) { 274 + if (slices[i].isRepost) { 261 275 slices.splice(i, 1) 262 276 } 263 277 } ··· 266 280 267 281 static removeQuotePosts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) { 268 282 for (let i = slices.length - 1; i >= 0; i--) { 269 - const embed = slices[i].rootItem.post.embed 270 - if ( 271 - AppBskyEmbedRecord.isView(embed) || 272 - AppBskyEmbedRecordWithMedia.isView(embed) 273 - ) { 283 + if (slices[i].isQuotePost) { 274 284 slices.splice(i, 1) 275 285 } 276 286 } ··· 315 325 // remove any replies without at least minLikes likes 316 326 for (let i = slices.length - 1; i >= 0; i--) { 317 327 const slice = slices[i] 318 - if (slice.isFullThread || !slice.isReply) { 319 - continue 320 - } 321 - 322 - const item = slice.rootItem 323 - const isRepost = Boolean(item.reason) 324 - if (isRepost) { 325 - continue 326 - } 327 - if ((item.post.likeCount || 0) < minLikes) { 328 - slices.splice(i, 1) 329 - } else if (followedOnly && !slice.isFollowingAllAuthors(userDid)) { 330 - slices.splice(i, 1) 328 + if (slice.isReply) { 329 + if (slice.isThread && slice.includesThreadRoot) { 330 + continue 331 + } 332 + if (slice.isRepost) { 333 + continue 334 + } 335 + if (slice.likeCount < minLikes) { 336 + slices.splice(i, 1) 337 + } else if (followedOnly && !slice.isFollowingAllAuthors(userDid)) { 338 + slices.splice(i, 1) 339 + } 331 340 } 332 341 } 333 342 return slices
+1 -1
src/lib/api/feed/merge.ts
··· 251 251 dryRun: false, 252 252 maintainOrder: true, 253 253 }) 254 - res.data.feed = slices.map(slice => slice.rootItem) 254 + res.data.feed = slices.map(slice => slice._feedPost) 255 255 return res 256 256 } 257 257 }
+4 -7
src/state/queries/post-feed.ts
··· 314 314 if (isDiscover) { 315 315 userActionHistory.seen( 316 316 slice.items.map(item => ({ 317 - feedContext: item.feedContext, 317 + feedContext: slice.feedContext, 318 318 likeCount: item.post.likeCount ?? 0, 319 319 repostCount: item.post.repostCount ?? 0, 320 320 replyCount: item.post.replyCount ?? 0, ··· 329 329 const feedPostSlice: FeedPostSlice = { 330 330 _reactKey: slice._reactKey, 331 331 _isFeedPostSlice: true, 332 - rootUri: slice.rootItem.post.uri, 332 + rootUri: slice.uri, 333 333 isThread: 334 334 slice.items.length > 1 && 335 335 slice.items.every( ··· 365 365 uri: item.post.uri, 366 366 post: item.post, 367 367 record: item.post.record, 368 - reason: 369 - i === 0 && slice.source 370 - ? slice.source 371 - : item.reason, 372 - feedContext: item.feedContext || slice.feedContext, 368 + reason: slice.reason, 369 + feedContext: slice.feedContext, 373 370 moderation: moderations[i], 374 371 parentAuthor, 375 372 isParentBlocked,