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 diffzio/stable
parent
3914025227
commit
ac1538baad
|
@ -14,29 +14,33 @@ export type FeedTunerFn = (
|
||||||
slices: FeedViewPostsSlice[],
|
slices: FeedViewPostsSlice[],
|
||||||
) => FeedViewPostsSlice[]
|
) => FeedViewPostsSlice[]
|
||||||
|
|
||||||
|
type FeedSliceItem = {
|
||||||
|
post: AppBskyFeedDefs.PostView
|
||||||
|
reply?: AppBskyFeedDefs.ReplyRef
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSliceItem(feedViewPost: FeedViewPost): FeedSliceItem {
|
||||||
|
return {
|
||||||
|
post: feedViewPost.post,
|
||||||
|
reply: feedViewPost.reply,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class FeedViewPostsSlice {
|
export class FeedViewPostsSlice {
|
||||||
_reactKey: string
|
_reactKey: string
|
||||||
isFlattenedReply = false
|
_feedPost: FeedViewPost
|
||||||
|
items: FeedSliceItem[]
|
||||||
|
|
||||||
constructor(public items: FeedViewPost[]) {
|
constructor(feedPost: FeedViewPost) {
|
||||||
const item = items[0]
|
this._feedPost = feedPost
|
||||||
this._reactKey = `slice-${item.post.uri}-${
|
this._reactKey = `slice-${feedPost.post.uri}-${
|
||||||
item.reason?.indexedAt || item.post.indexedAt
|
feedPost.reason?.indexedAt || feedPost.post.indexedAt
|
||||||
}`
|
}`
|
||||||
|
this.items = [toSliceItem(feedPost)]
|
||||||
}
|
}
|
||||||
|
|
||||||
get uri() {
|
get uri() {
|
||||||
if (this.isFlattenedReply) {
|
return this._feedPost.post.uri
|
||||||
return this.items[1].post.uri
|
|
||||||
}
|
|
||||||
return this.items[0].post.uri
|
|
||||||
}
|
|
||||||
|
|
||||||
get ts() {
|
|
||||||
if (this.items[0].reason?.indexedAt) {
|
|
||||||
return this.items[0].reason.indexedAt as string
|
|
||||||
}
|
|
||||||
return this.items[0].post.indexedAt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isThread() {
|
get isThread() {
|
||||||
|
@ -48,31 +52,42 @@ export class FeedViewPostsSlice {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFullThread() {
|
get isQuotePost() {
|
||||||
return this.isThread && !this.items[0].reply
|
const embed = this._feedPost.post.embed
|
||||||
}
|
return (
|
||||||
|
AppBskyEmbedRecord.isView(embed) ||
|
||||||
get rootItem() {
|
AppBskyEmbedRecordWithMedia.isView(embed)
|
||||||
if (this.isFlattenedReply) {
|
)
|
||||||
return this.items[1]
|
|
||||||
}
|
|
||||||
return this.items[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isReply() {
|
get isReply() {
|
||||||
return (
|
return (
|
||||||
AppBskyFeedPost.isRecord(this.rootItem.post.record) &&
|
AppBskyFeedPost.isRecord(this._feedPost.post.record) &&
|
||||||
!!this.rootItem.post.record.reply
|
!!this._feedPost.post.record.reply
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get source(): ReasonFeedSource | undefined {
|
get reason() {
|
||||||
return this.items.find(item => '__source' in item && !!item.__source)
|
return '__source' in this._feedPost
|
||||||
?.__source as ReasonFeedSource
|
? (this._feedPost.__source as ReasonFeedSource)
|
||||||
|
: this._feedPost.reason
|
||||||
}
|
}
|
||||||
|
|
||||||
get feedContext() {
|
get feedContext() {
|
||||||
return this.items.find(item => item.feedContext)?.feedContext
|
return this._feedPost.feedContext
|
||||||
|
}
|
||||||
|
|
||||||
|
get isRepost() {
|
||||||
|
const reason = this._feedPost.reason
|
||||||
|
return AppBskyFeedDefs.isReasonRepost(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
get includesThreadRoot() {
|
||||||
|
return !this.items[0].reply
|
||||||
|
}
|
||||||
|
|
||||||
|
get likeCount() {
|
||||||
|
return this._feedPost.post.likeCount ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
containsUri(uri: string) {
|
containsUri(uri: string) {
|
||||||
|
@ -97,24 +112,24 @@ export class FeedViewPostsSlice {
|
||||||
if (this.items[0].reply) {
|
if (this.items[0].reply) {
|
||||||
const reply = this.items[0].reply
|
const reply = this.items[0].reply
|
||||||
if (AppBskyFeedDefs.isPostView(reply.parent)) {
|
if (AppBskyFeedDefs.isPostView(reply.parent)) {
|
||||||
this.isFlattenedReply = true
|
|
||||||
this.items.splice(0, 0, {post: reply.parent})
|
this.items.splice(0, 0, {post: reply.parent})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isFollowingAllAuthors(userDid: string) {
|
isFollowingAllAuthors(userDid: string) {
|
||||||
const item = this.rootItem
|
const feedPost = this._feedPost
|
||||||
if (item.post.author.did === userDid) {
|
if (feedPost.post.author.did === userDid) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (AppBskyFeedDefs.isPostView(item.reply?.parent)) {
|
if (AppBskyFeedDefs.isPostView(feedPost.reply?.parent)) {
|
||||||
const parent = item.reply?.parent
|
const parent = feedPost.reply?.parent
|
||||||
if (parent?.author.did === userDid) {
|
if (parent?.author.did === userDid) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
parent?.author.viewer?.following && item.post.author.viewer?.following
|
parent?.author.viewer?.following &&
|
||||||
|
feedPost.post.author.viewer?.following
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -127,7 +142,7 @@ export class NoopFeedTuner {
|
||||||
feed: FeedViewPost[],
|
feed: FeedViewPost[],
|
||||||
_opts?: {dryRun: boolean; maintainOrder: boolean},
|
_opts?: {dryRun: boolean; maintainOrder: boolean},
|
||||||
): FeedViewPostsSlice[] {
|
): FeedViewPostsSlice[] {
|
||||||
return feed.map(item => new FeedViewPostsSlice([item]))
|
return feed.map(item => new FeedViewPostsSlice(item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +180,7 @@ export class FeedTuner {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (maintainOrder) {
|
if (maintainOrder) {
|
||||||
slices = feed.map(item => new FeedViewPostsSlice([item]))
|
slices = feed.map(item => new FeedViewPostsSlice(item))
|
||||||
} else {
|
} else {
|
||||||
// arrange the posts into thread slices
|
// arrange the posts into thread slices
|
||||||
for (let i = feed.length - 1; i >= 0; i--) {
|
for (let i = feed.length - 1; i >= 0; i--) {
|
||||||
|
@ -192,7 +207,7 @@ export class FeedTuner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slices.unshift(new FeedViewPostsSlice([item]))
|
slices.unshift(new FeedViewPostsSlice(item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +230,7 @@ export class FeedTuner {
|
||||||
|
|
||||||
// turn non-threads with reply parents into threads
|
// turn non-threads with reply parents into threads
|
||||||
for (const slice of slices) {
|
for (const slice of slices) {
|
||||||
if (!slice.isThread && !slice.items[0].reason && slice.items[0].reply) {
|
if (!slice.isThread && !slice.reason && slice.items[0].reply) {
|
||||||
const reply = slice.items[0].reply
|
const reply = slice.items[0].reply
|
||||||
if (
|
if (
|
||||||
AppBskyFeedDefs.isPostView(reply.parent) &&
|
AppBskyFeedDefs.isPostView(reply.parent) &&
|
||||||
|
@ -256,8 +271,7 @@ export class FeedTuner {
|
||||||
|
|
||||||
static removeReposts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
static removeReposts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
||||||
for (let i = slices.length - 1; i >= 0; i--) {
|
for (let i = slices.length - 1; i >= 0; i--) {
|
||||||
const reason = slices[i].rootItem.reason
|
if (slices[i].isRepost) {
|
||||||
if (AppBskyFeedDefs.isReasonRepost(reason)) {
|
|
||||||
slices.splice(i, 1)
|
slices.splice(i, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -266,11 +280,7 @@ export class FeedTuner {
|
||||||
|
|
||||||
static removeQuotePosts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
static removeQuotePosts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
||||||
for (let i = slices.length - 1; i >= 0; i--) {
|
for (let i = slices.length - 1; i >= 0; i--) {
|
||||||
const embed = slices[i].rootItem.post.embed
|
if (slices[i].isQuotePost) {
|
||||||
if (
|
|
||||||
AppBskyEmbedRecord.isView(embed) ||
|
|
||||||
AppBskyEmbedRecordWithMedia.isView(embed)
|
|
||||||
) {
|
|
||||||
slices.splice(i, 1)
|
slices.splice(i, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,21 +325,20 @@ export class FeedTuner {
|
||||||
// remove any replies without at least minLikes likes
|
// remove any replies without at least minLikes likes
|
||||||
for (let i = slices.length - 1; i >= 0; i--) {
|
for (let i = slices.length - 1; i >= 0; i--) {
|
||||||
const slice = slices[i]
|
const slice = slices[i]
|
||||||
if (slice.isFullThread || !slice.isReply) {
|
if (slice.isReply) {
|
||||||
|
if (slice.isThread && slice.includesThreadRoot) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if (slice.isRepost) {
|
||||||
const item = slice.rootItem
|
|
||||||
const isRepost = Boolean(item.reason)
|
|
||||||
if (isRepost) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ((item.post.likeCount || 0) < minLikes) {
|
if (slice.likeCount < minLikes) {
|
||||||
slices.splice(i, 1)
|
slices.splice(i, 1)
|
||||||
} else if (followedOnly && !slice.isFollowingAllAuthors(userDid)) {
|
} else if (followedOnly && !slice.isFollowingAllAuthors(userDid)) {
|
||||||
slices.splice(i, 1)
|
slices.splice(i, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return slices
|
return slices
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,7 +251,7 @@ class MergeFeedSource_Following extends MergeFeedSource {
|
||||||
dryRun: false,
|
dryRun: false,
|
||||||
maintainOrder: true,
|
maintainOrder: true,
|
||||||
})
|
})
|
||||||
res.data.feed = slices.map(slice => slice.rootItem)
|
res.data.feed = slices.map(slice => slice._feedPost)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -314,7 +314,7 @@ export function usePostFeedQuery(
|
||||||
if (isDiscover) {
|
if (isDiscover) {
|
||||||
userActionHistory.seen(
|
userActionHistory.seen(
|
||||||
slice.items.map(item => ({
|
slice.items.map(item => ({
|
||||||
feedContext: item.feedContext,
|
feedContext: slice.feedContext,
|
||||||
likeCount: item.post.likeCount ?? 0,
|
likeCount: item.post.likeCount ?? 0,
|
||||||
repostCount: item.post.repostCount ?? 0,
|
repostCount: item.post.repostCount ?? 0,
|
||||||
replyCount: item.post.replyCount ?? 0,
|
replyCount: item.post.replyCount ?? 0,
|
||||||
|
@ -329,7 +329,7 @@ export function usePostFeedQuery(
|
||||||
const feedPostSlice: FeedPostSlice = {
|
const feedPostSlice: FeedPostSlice = {
|
||||||
_reactKey: slice._reactKey,
|
_reactKey: slice._reactKey,
|
||||||
_isFeedPostSlice: true,
|
_isFeedPostSlice: true,
|
||||||
rootUri: slice.rootItem.post.uri,
|
rootUri: slice.uri,
|
||||||
isThread:
|
isThread:
|
||||||
slice.items.length > 1 &&
|
slice.items.length > 1 &&
|
||||||
slice.items.every(
|
slice.items.every(
|
||||||
|
@ -365,11 +365,8 @@ export function usePostFeedQuery(
|
||||||
uri: item.post.uri,
|
uri: item.post.uri,
|
||||||
post: item.post,
|
post: item.post,
|
||||||
record: item.post.record,
|
record: item.post.record,
|
||||||
reason:
|
reason: slice.reason,
|
||||||
i === 0 && slice.source
|
feedContext: slice.feedContext,
|
||||||
? slice.source
|
|
||||||
: item.reason,
|
|
||||||
feedContext: item.feedContext || slice.feedContext,
|
|
||||||
moderation: moderations[i],
|
moderation: moderations[i],
|
||||||
parentAuthor,
|
parentAuthor,
|
||||||
isParentBlocked,
|
isParentBlocked,
|
||||||
|
|
Loading…
Reference in New Issue