Merge remote-tracking branch 'origin/main' into reply-qp-moderation
* origin/main: (30 commits) Show just-posted replies above OP replies (#4901) Remove client filtering of starter packs (#4753) Remove show_avi_follow_button (#4900) Remove native_pwi_disabled (#4896) Fix overflow on posts (#4899) Move onPressReply into child component (#4898) Remove new_user_progress_guide (#4895) Remove explore_page_profile_card_social_proof (#4894) Remove ungroup_follow_backs gate (#4893) Remove unnecessary state update for reply gate (#4897) Include follow-based suggestions in interstitial (#4889) Cleanup flags (#4891) ALF suggested follows in profile header (#4828) Added trans (#4890) Keep interstitial fresh on refresh (#4888) Include popcluster in suggestion ranking (#4887) Add logging of selected feed preference when displaying the following feed (#4789) [Video] Visibility detection view (#4741) [Videos] Video player - PR #2 - better web support (#4732) [Video] Authed video upload (#4885) ...
This commit is contained in:
+294
-198
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyFeedDefs,
|
||||
@@ -6,50 +7,125 @@ import {
|
||||
} from '@atproto/api'
|
||||
|
||||
import {isPostInLanguage} from '../../locale/helpers'
|
||||
import {FALLBACK_MARKER_POST} from './feed/home'
|
||||
import {ReasonFeedSource} from './feed/types'
|
||||
|
||||
type FeedViewPost = AppBskyFeedDefs.FeedViewPost
|
||||
|
||||
export type FeedTunerFn = (
|
||||
tuner: FeedTuner,
|
||||
slices: FeedViewPostsSlice[],
|
||||
dryRun: boolean,
|
||||
) => FeedViewPostsSlice[]
|
||||
|
||||
type FeedSliceItem = {
|
||||
post: AppBskyFeedDefs.PostView
|
||||
reply?: AppBskyFeedDefs.ReplyRef
|
||||
record: AppBskyFeedPost.Record
|
||||
parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
isParentBlocked: boolean
|
||||
}
|
||||
|
||||
function toSliceItem(feedViewPost: FeedViewPost): FeedSliceItem {
|
||||
return {
|
||||
post: feedViewPost.post,
|
||||
reply: feedViewPost.reply,
|
||||
}
|
||||
type AuthorContext = {
|
||||
author: AppBskyActorDefs.ProfileViewBasic
|
||||
parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
grandparentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
rootAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
}
|
||||
|
||||
export class FeedViewPostsSlice {
|
||||
_reactKey: string
|
||||
_feedPost: FeedViewPost
|
||||
items: FeedSliceItem[]
|
||||
isIncompleteThread: boolean
|
||||
isFallbackMarker: boolean
|
||||
isOrphan: boolean
|
||||
rootUri: string
|
||||
|
||||
constructor(feedPost: FeedViewPost) {
|
||||
const {post, reply, reason} = feedPost
|
||||
this.items = []
|
||||
this.isIncompleteThread = false
|
||||
this.isFallbackMarker = false
|
||||
this.isOrphan = false
|
||||
if (AppBskyFeedDefs.isPostView(reply?.root)) {
|
||||
this.rootUri = reply.root.uri
|
||||
} else {
|
||||
this.rootUri = post.uri
|
||||
}
|
||||
this._feedPost = feedPost
|
||||
this._reactKey = `slice-${feedPost.post.uri}-${
|
||||
feedPost.reason?.indexedAt || feedPost.post.indexedAt
|
||||
this._reactKey = `slice-${post.uri}-${
|
||||
feedPost.reason?.indexedAt || post.indexedAt
|
||||
}`
|
||||
this.items = [toSliceItem(feedPost)]
|
||||
}
|
||||
|
||||
get uri() {
|
||||
return this._feedPost.post.uri
|
||||
}
|
||||
|
||||
get isThread() {
|
||||
return (
|
||||
this.items.length > 1 &&
|
||||
this.items.every(
|
||||
item => item.post.author.did === this.items[0].post.author.did,
|
||||
)
|
||||
if (feedPost.post.uri === FALLBACK_MARKER_POST.post.uri) {
|
||||
this.isFallbackMarker = true
|
||||
return
|
||||
}
|
||||
if (
|
||||
!AppBskyFeedPost.isRecord(post.record) ||
|
||||
!AppBskyFeedPost.validateRecord(post.record).success
|
||||
) {
|
||||
return
|
||||
}
|
||||
const parent = reply?.parent
|
||||
const isParentBlocked = AppBskyFeedDefs.isBlockedPost(parent)
|
||||
let parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
if (AppBskyFeedDefs.isPostView(parent)) {
|
||||
parentAuthor = parent.author
|
||||
}
|
||||
this.items.push({
|
||||
post,
|
||||
record: post.record,
|
||||
parentAuthor,
|
||||
isParentBlocked,
|
||||
})
|
||||
if (!reply || reason) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
!AppBskyFeedDefs.isPostView(parent) ||
|
||||
!AppBskyFeedPost.isRecord(parent.record) ||
|
||||
!AppBskyFeedPost.validateRecord(parent.record).success
|
||||
) {
|
||||
this.isOrphan = true
|
||||
return
|
||||
}
|
||||
const grandparentAuthor = reply.grandparentAuthor
|
||||
const isGrandparentBlocked = Boolean(
|
||||
grandparentAuthor?.viewer?.blockedBy ||
|
||||
grandparentAuthor?.viewer?.blocking ||
|
||||
grandparentAuthor?.viewer?.blockingByList,
|
||||
)
|
||||
this.items.unshift({
|
||||
post: parent,
|
||||
record: parent.record,
|
||||
parentAuthor: grandparentAuthor,
|
||||
isParentBlocked: isGrandparentBlocked,
|
||||
})
|
||||
if (isGrandparentBlocked) {
|
||||
this.isOrphan = true
|
||||
// Keep going, it might still have a root.
|
||||
}
|
||||
const root = reply.root
|
||||
if (
|
||||
!AppBskyFeedDefs.isPostView(root) ||
|
||||
!AppBskyFeedPost.isRecord(root.record) ||
|
||||
!AppBskyFeedPost.validateRecord(root.record).success
|
||||
) {
|
||||
this.isOrphan = true
|
||||
return
|
||||
}
|
||||
if (root.uri === parent.uri) {
|
||||
return
|
||||
}
|
||||
this.items.unshift({
|
||||
post: root,
|
||||
record: root.record,
|
||||
isParentBlocked: false,
|
||||
parentAuthor: undefined,
|
||||
})
|
||||
if (parent.record.reply?.parent.uri !== root.uri) {
|
||||
this.isIncompleteThread = true
|
||||
}
|
||||
}
|
||||
|
||||
get isQuotePost() {
|
||||
@@ -90,204 +166,171 @@ export class FeedViewPostsSlice {
|
||||
return !!this.items.find(item => item.post.uri === uri)
|
||||
}
|
||||
|
||||
isNextInThread(uri: string) {
|
||||
return this.items[this.items.length - 1].post.uri === uri
|
||||
}
|
||||
|
||||
insert(item: FeedViewPost) {
|
||||
const selfReplyUri = getSelfReplyUri(item)
|
||||
const i = this.items.findIndex(item2 => item2.post.uri === selfReplyUri)
|
||||
if (i !== -1) {
|
||||
this.items.splice(i + 1, 0, item)
|
||||
} else {
|
||||
this.items.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
flattenReplyParent() {
|
||||
if (this.items[0].reply) {
|
||||
const reply = this.items[0].reply
|
||||
if (AppBskyFeedDefs.isPostView(reply.parent)) {
|
||||
this.items.splice(0, 0, {post: reply.parent})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isFollowingAllAuthors(userDid: string) {
|
||||
getAuthors(): AuthorContext {
|
||||
const feedPost = this._feedPost
|
||||
const authors = [feedPost.post.author]
|
||||
let author: AppBskyActorDefs.ProfileViewBasic = feedPost.post.author
|
||||
let parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
let grandparentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
let rootAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
if (feedPost.reply) {
|
||||
if (AppBskyFeedDefs.isPostView(feedPost.reply.parent)) {
|
||||
authors.push(feedPost.reply.parent.author)
|
||||
parentAuthor = feedPost.reply.parent.author
|
||||
}
|
||||
if (feedPost.reply.grandparentAuthor) {
|
||||
authors.push(feedPost.reply.grandparentAuthor)
|
||||
grandparentAuthor = feedPost.reply.grandparentAuthor
|
||||
}
|
||||
if (AppBskyFeedDefs.isPostView(feedPost.reply.root)) {
|
||||
authors.push(feedPost.reply.root.author)
|
||||
rootAuthor = feedPost.reply.root.author
|
||||
}
|
||||
}
|
||||
return authors.every(a => a.did === userDid || a.viewer?.following)
|
||||
return {
|
||||
author,
|
||||
parentAuthor,
|
||||
grandparentAuthor,
|
||||
rootAuthor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class FeedTuner {
|
||||
seenKeys: Set<string> = new Set()
|
||||
seenUris: Set<string> = new Set()
|
||||
seenRootUris: Set<string> = new Set()
|
||||
|
||||
constructor(public tunerFns: FeedTunerFn[]) {}
|
||||
|
||||
reset() {
|
||||
this.seenKeys.clear()
|
||||
this.seenUris.clear()
|
||||
}
|
||||
|
||||
tune(
|
||||
feed: FeedViewPost[],
|
||||
{dryRun, maintainOrder}: {dryRun: boolean; maintainOrder: boolean} = {
|
||||
{dryRun}: {dryRun: boolean} = {
|
||||
dryRun: false,
|
||||
maintainOrder: false,
|
||||
},
|
||||
): FeedViewPostsSlice[] {
|
||||
let slices: FeedViewPostsSlice[] = []
|
||||
let slices: FeedViewPostsSlice[] = feed
|
||||
.map(item => new FeedViewPostsSlice(item))
|
||||
.filter(s => s.items.length > 0 || s.isFallbackMarker)
|
||||
|
||||
// remove posts that are replies, but which don't have the parent
|
||||
// hydrated. this means the parent was either deleted or blocked
|
||||
feed = feed.filter(item => {
|
||||
if (
|
||||
AppBskyFeedPost.isRecord(item.post.record) &&
|
||||
item.post.record.reply &&
|
||||
!item.reply
|
||||
) {
|
||||
// run the custom tuners
|
||||
for (const tunerFn of this.tunerFns) {
|
||||
slices = tunerFn(this, slices.slice(), dryRun)
|
||||
}
|
||||
|
||||
slices = slices.filter(slice => {
|
||||
if (this.seenKeys.has(slice._reactKey)) {
|
||||
return false
|
||||
}
|
||||
// Some feeds, like Following, dedupe by thread, so you only see the most recent reply.
|
||||
// However, we don't want per-thread dedupe for author feeds (where we need to show every post)
|
||||
// or for feedgens (where we want to let the feed serve multiple replies if it chooses to).
|
||||
// To avoid showing the same context (root and/or parent) more than once, we do last resort
|
||||
// per-post deduplication. It hides already seen posts as long as this doesn't break the thread.
|
||||
for (let i = 0; i < slice.items.length; i++) {
|
||||
const item = slice.items[i]
|
||||
if (this.seenUris.has(item.post.uri)) {
|
||||
if (i === 0) {
|
||||
// Omit contiguous seen leading items.
|
||||
// For example, [A -> B -> C], [A -> D -> E], [A -> D -> F]
|
||||
// would turn into [A -> B -> C], [D -> E], [F].
|
||||
slice.items.splice(0, 1)
|
||||
i--
|
||||
}
|
||||
if (i === slice.items.length - 1) {
|
||||
// If the last item in the slice was already seen, omit the whole slice.
|
||||
// This means we'd miss its parents, but the user can "show more" to see them.
|
||||
// For example, [A ... E -> F], [A ... D -> E], [A ... C -> D], [A -> B -> C]
|
||||
// would get collapsed into [A ... E -> F], with B/C/D considered seen.
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (!dryRun) {
|
||||
this.seenUris.add(item.post.uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!dryRun) {
|
||||
this.seenKeys.add(slice._reactKey)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if (maintainOrder) {
|
||||
slices = feed.map(item => new FeedViewPostsSlice(item))
|
||||
} else {
|
||||
// arrange the posts into thread slices
|
||||
for (let i = feed.length - 1; i >= 0; i--) {
|
||||
const item = feed[i]
|
||||
|
||||
const selfReplyUri = getSelfReplyUri(item)
|
||||
if (selfReplyUri) {
|
||||
const index = slices.findIndex(slice =>
|
||||
slice.isNextInThread(selfReplyUri),
|
||||
)
|
||||
|
||||
if (index !== -1) {
|
||||
const parent = slices[index]
|
||||
|
||||
parent.insert(item)
|
||||
|
||||
// If our slice isn't currently on the top, reinsert it to the top.
|
||||
if (index !== 0) {
|
||||
slices.splice(index, 1)
|
||||
slices.unshift(parent)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
slices.unshift(new FeedViewPostsSlice(item))
|
||||
}
|
||||
}
|
||||
|
||||
// run the custom tuners
|
||||
for (const tunerFn of this.tunerFns) {
|
||||
slices = tunerFn(this, slices.slice())
|
||||
}
|
||||
|
||||
// remove any items already "seen"
|
||||
const soonToBeSeenUris: Set<string> = new Set()
|
||||
for (let i = slices.length - 1; i >= 0; i--) {
|
||||
if (!slices[i].isThread && this.seenUris.has(slices[i].uri)) {
|
||||
slices.splice(i, 1)
|
||||
} else {
|
||||
for (const item of slices[i].items) {
|
||||
soonToBeSeenUris.add(item.post.uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// turn non-threads with reply parents into threads
|
||||
for (const slice of slices) {
|
||||
if (!slice.isThread && !slice.reason && slice.items[0].reply) {
|
||||
const reply = slice.items[0].reply
|
||||
if (
|
||||
AppBskyFeedDefs.isPostView(reply.parent) &&
|
||||
!this.seenUris.has(reply.parent.uri) &&
|
||||
!soonToBeSeenUris.has(reply.parent.uri)
|
||||
) {
|
||||
const uri = reply.parent.uri
|
||||
slice.flattenReplyParent()
|
||||
soonToBeSeenUris.add(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
slices = slices.filter(slice => {
|
||||
if (this.seenKeys.has(slice._reactKey)) {
|
||||
return false
|
||||
}
|
||||
for (const item of slice.items) {
|
||||
this.seenUris.add(item.post.uri)
|
||||
}
|
||||
this.seenKeys.add(slice._reactKey)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return slices
|
||||
}
|
||||
|
||||
static removeReplies(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
||||
for (let i = slices.length - 1; i >= 0; i--) {
|
||||
if (slices[i].isReply) {
|
||||
slices.splice(i, 1)
|
||||
}
|
||||
}
|
||||
return slices
|
||||
}
|
||||
|
||||
static removeReposts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
||||
for (let i = slices.length - 1; i >= 0; i--) {
|
||||
if (slices[i].isRepost) {
|
||||
slices.splice(i, 1)
|
||||
}
|
||||
}
|
||||
return slices
|
||||
}
|
||||
|
||||
static removeQuotePosts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
||||
for (let i = slices.length - 1; i >= 0; i--) {
|
||||
if (slices[i].isQuotePost) {
|
||||
slices.splice(i, 1)
|
||||
}
|
||||
}
|
||||
return slices
|
||||
}
|
||||
|
||||
static dedupReposts(
|
||||
static removeReplies(
|
||||
tuner: FeedTuner,
|
||||
slices: FeedViewPostsSlice[],
|
||||
): FeedViewPostsSlice[] {
|
||||
// remove duplicates caused by reposts
|
||||
_dryRun: boolean,
|
||||
) {
|
||||
for (let i = 0; i < slices.length; i++) {
|
||||
const item1 = slices[i]
|
||||
for (let j = i + 1; j < slices.length; j++) {
|
||||
const item2 = slices[j]
|
||||
if (item2.isThread) {
|
||||
// dont dedup items that are rendering in a thread as this can cause rendering errors
|
||||
continue
|
||||
}
|
||||
if (item1.containsUri(item2.items[0].post.uri)) {
|
||||
slices.splice(j, 1)
|
||||
j--
|
||||
const slice = slices[i]
|
||||
if (
|
||||
slice.isReply &&
|
||||
!slice.isRepost &&
|
||||
// This is not perfect but it's close as we can get to
|
||||
// detecting threads without having to peek ahead.
|
||||
!areSameAuthor(slice.getAuthors())
|
||||
) {
|
||||
slices.splice(i, 1)
|
||||
i--
|
||||
}
|
||||
}
|
||||
return slices
|
||||
}
|
||||
|
||||
static removeReposts(
|
||||
tuner: FeedTuner,
|
||||
slices: FeedViewPostsSlice[],
|
||||
_dryRun: boolean,
|
||||
) {
|
||||
for (let i = 0; i < slices.length; i++) {
|
||||
if (slices[i].isRepost) {
|
||||
slices.splice(i, 1)
|
||||
i--
|
||||
}
|
||||
}
|
||||
return slices
|
||||
}
|
||||
|
||||
static removeQuotePosts(
|
||||
tuner: FeedTuner,
|
||||
slices: FeedViewPostsSlice[],
|
||||
_dryRun: boolean,
|
||||
) {
|
||||
for (let i = 0; i < slices.length; i++) {
|
||||
if (slices[i].isQuotePost) {
|
||||
slices.splice(i, 1)
|
||||
i--
|
||||
}
|
||||
}
|
||||
return slices
|
||||
}
|
||||
|
||||
static removeOrphans(
|
||||
tuner: FeedTuner,
|
||||
slices: FeedViewPostsSlice[],
|
||||
_dryRun: boolean,
|
||||
) {
|
||||
for (let i = 0; i < slices.length; i++) {
|
||||
if (slices[i].isOrphan) {
|
||||
slices.splice(i, 1)
|
||||
i--
|
||||
}
|
||||
}
|
||||
return slices
|
||||
}
|
||||
|
||||
static dedupThreads(
|
||||
tuner: FeedTuner,
|
||||
slices: FeedViewPostsSlice[],
|
||||
dryRun: boolean,
|
||||
): FeedViewPostsSlice[] {
|
||||
for (let i = 0; i < slices.length; i++) {
|
||||
const rootUri = slices[i].rootUri
|
||||
if (!slices[i].isRepost && tuner.seenRootUris.has(rootUri)) {
|
||||
slices.splice(i, 1)
|
||||
i--
|
||||
} else {
|
||||
if (!dryRun) {
|
||||
tuner.seenRootUris.add(rootUri)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,15 +341,17 @@ export class FeedTuner {
|
||||
return (
|
||||
tuner: FeedTuner,
|
||||
slices: FeedViewPostsSlice[],
|
||||
_dryRun: boolean,
|
||||
): FeedViewPostsSlice[] => {
|
||||
for (let i = slices.length - 1; i >= 0; i--) {
|
||||
for (let i = 0; i < slices.length; i++) {
|
||||
const slice = slices[i]
|
||||
if (
|
||||
slice.isReply &&
|
||||
!slice.isRepost &&
|
||||
!slice.isFollowingAllAuthors(userDid)
|
||||
!shouldDisplayReplyInFollowing(slice.getAuthors(), userDid)
|
||||
) {
|
||||
slices.splice(i, 1)
|
||||
i--
|
||||
}
|
||||
}
|
||||
return slices
|
||||
@@ -324,6 +369,7 @@ export class FeedTuner {
|
||||
return (
|
||||
tuner: FeedTuner,
|
||||
slices: FeedViewPostsSlice[],
|
||||
_dryRun: boolean,
|
||||
): FeedViewPostsSlice[] => {
|
||||
const candidateSlices = slices.slice()
|
||||
|
||||
@@ -332,7 +378,7 @@ export class FeedTuner {
|
||||
return slices
|
||||
}
|
||||
|
||||
for (let i = slices.length - 1; i >= 0; i--) {
|
||||
for (let i = 0; i < slices.length; i++) {
|
||||
let hasPreferredLang = false
|
||||
for (const item of slices[i].items) {
|
||||
if (isPostInLanguage(item.post, preferredLangsCode2)) {
|
||||
@@ -358,16 +404,66 @@ export class FeedTuner {
|
||||
}
|
||||
}
|
||||
|
||||
function getSelfReplyUri(item: FeedViewPost): string | undefined {
|
||||
if (item.reply) {
|
||||
if (
|
||||
AppBskyFeedDefs.isPostView(item.reply.parent) &&
|
||||
!AppBskyFeedDefs.isReasonRepost(item.reason) // don't thread reposted self-replies
|
||||
) {
|
||||
return item.reply.parent.author.did === item.post.author.did
|
||||
? item.reply.parent.uri
|
||||
: undefined
|
||||
}
|
||||
function areSameAuthor(authors: AuthorContext): boolean {
|
||||
const {author, parentAuthor, grandparentAuthor, rootAuthor} = authors
|
||||
const authorDid = author.did
|
||||
if (parentAuthor && parentAuthor.did !== authorDid) {
|
||||
return false
|
||||
}
|
||||
return undefined
|
||||
if (grandparentAuthor && grandparentAuthor.did !== authorDid) {
|
||||
return false
|
||||
}
|
||||
if (rootAuthor && rootAuthor.did !== authorDid) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function shouldDisplayReplyInFollowing(
|
||||
authors: AuthorContext,
|
||||
userDid: string,
|
||||
): boolean {
|
||||
const {author, parentAuthor, grandparentAuthor, rootAuthor} = authors
|
||||
if (!isSelfOrFollowing(author, userDid)) {
|
||||
// Only show replies from self or people you follow.
|
||||
return false
|
||||
}
|
||||
if (
|
||||
(!parentAuthor || parentAuthor.did === author.did) &&
|
||||
(!rootAuthor || rootAuthor.did === author.did) &&
|
||||
(!grandparentAuthor || grandparentAuthor.did === author.did)
|
||||
) {
|
||||
// Always show self-threads.
|
||||
return true
|
||||
}
|
||||
// From this point on we need at least one more reason to show it.
|
||||
if (
|
||||
parentAuthor &&
|
||||
parentAuthor.did !== author.did &&
|
||||
isSelfOrFollowing(parentAuthor, userDid)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
grandparentAuthor &&
|
||||
grandparentAuthor.did !== author.did &&
|
||||
isSelfOrFollowing(grandparentAuthor, userDid)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
rootAuthor &&
|
||||
rootAuthor.did !== author.did &&
|
||||
isSelfOrFollowing(rootAuthor, userDid)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function isSelfOrFollowing(
|
||||
profile: AppBskyActorDefs.ProfileViewBasic,
|
||||
userDid: string,
|
||||
) {
|
||||
return Boolean(profile.did === userDid || profile.viewer?.following)
|
||||
}
|
||||
|
||||
@@ -193,12 +193,6 @@ class MergeFeedSource {
|
||||
return this.hasMore && this.queue.length === 0
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.cursor = undefined
|
||||
this.queue = []
|
||||
this.hasMore = true
|
||||
}
|
||||
|
||||
take(n: number): AppBskyFeedDefs.FeedViewPost[] {
|
||||
return this.queue.splice(0, n)
|
||||
}
|
||||
@@ -232,11 +226,6 @@ class MergeFeedSource {
|
||||
class MergeFeedSource_Following extends MergeFeedSource {
|
||||
tuner = new FeedTuner(this.feedTuners)
|
||||
|
||||
reset() {
|
||||
super.reset()
|
||||
this.tuner.reset()
|
||||
}
|
||||
|
||||
async fetchNext(n: number) {
|
||||
return this._fetchNextInner(n)
|
||||
}
|
||||
@@ -249,7 +238,6 @@ class MergeFeedSource_Following extends MergeFeedSource {
|
||||
// run the tuner pre-emptively to ensure better mixing
|
||||
const slices = this.tuner.tune(res.data.feed, {
|
||||
dryRun: false,
|
||||
maintainOrder: true,
|
||||
})
|
||||
res.data.feed = slices.map(slice => slice._feedPost)
|
||||
return res
|
||||
|
||||
@@ -134,7 +134,7 @@ export function useModerationCauseDescription(
|
||||
}
|
||||
}
|
||||
if (def.identifier === 'porn' || def.identifier === 'sexual') {
|
||||
strings.name = 'Adult Content'
|
||||
strings.name = _(msg`Adult Content`)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {BskyAgent} from '@atproto/api'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {SessionAccount, useAgent, useSession} from '#/state/session'
|
||||
import {logEvent, useGate} from 'lib/statsig/statsig'
|
||||
import {logEvent} from 'lib/statsig/statsig'
|
||||
import {devicePlatform, isAndroid, isNative} from 'platform/detection'
|
||||
import BackgroundNotificationHandler from '../../../modules/expo-background-notification-handler'
|
||||
|
||||
@@ -86,7 +86,6 @@ export function useNotificationsRegistration() {
|
||||
}
|
||||
|
||||
export function useRequestNotificationsPermission() {
|
||||
const gate = useGate()
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
|
||||
@@ -102,16 +101,7 @@ export function useRequestNotificationsPermission() {
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
context === 'StartOnboarding' &&
|
||||
gate('request_notifications_permission_after_onboarding_v2')
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
context === 'AfterOnboarding' &&
|
||||
!gate('request_notifications_permission_after_onboarding_v2')
|
||||
) {
|
||||
if (context === 'AfterOnboarding') {
|
||||
return
|
||||
}
|
||||
if (context === 'Home' && !currentAccount) {
|
||||
|
||||
@@ -159,6 +159,7 @@ export type LogEvents = {
|
||||
| 'AvatarButton'
|
||||
| 'StarterPackProfilesList'
|
||||
| 'FeedInterstitial'
|
||||
| 'ProfileHeaderSuggestedFollows'
|
||||
}
|
||||
'profile:unfollow': {
|
||||
logContext:
|
||||
@@ -173,6 +174,7 @@ export type LogEvents = {
|
||||
| 'AvatarButton'
|
||||
| 'StarterPackProfilesList'
|
||||
| 'FeedInterstitial'
|
||||
| 'ProfileHeaderSuggestedFollows'
|
||||
}
|
||||
'chat:create': {
|
||||
logContext: 'ProfileHeader' | 'NewChatDialog' | 'SendViaChatDialog'
|
||||
@@ -211,6 +213,14 @@ export type LogEvents = {
|
||||
'feed:interstitial:profileCard:press': {}
|
||||
'feed:interstitial:feedCard:press': {}
|
||||
|
||||
'profile:header:suggestedFollowsCard:press': {}
|
||||
|
||||
'debug:followingPrefs': {
|
||||
followingShowRepliesFromPref: 'all' | 'following' | 'off'
|
||||
followingRepliesMinLikePref: number
|
||||
}
|
||||
'debug:followingDisplayed': {}
|
||||
|
||||
'test:all:always': {}
|
||||
'test:all:sometimes': {}
|
||||
'test:all:boosted_by_gate1': {reason: 'base' | 'gate1'}
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
export type Gate =
|
||||
// Keep this alphabetic please.
|
||||
| 'debug_show_feedcontext'
|
||||
| 'explore_page_profile_card_social_proof'
|
||||
| 'native_pwi_disabled'
|
||||
| 'new_user_guided_tour'
|
||||
| 'new_user_progress_guide'
|
||||
| 'onboarding_minimum_interests'
|
||||
| 'request_notifications_permission_after_onboarding_v2'
|
||||
| 'session_withproxy_fix'
|
||||
| 'show_avi_follow_button'
|
||||
| 'show_follow_back_label_v2'
|
||||
| 'suggested_feeds_interstitial'
|
||||
| 'suggested_follows_interstitial'
|
||||
| 'ungroup_follow_backs'
|
||||
| 'video_debug'
|
||||
| 'videos'
|
||||
| 'small_avi_thumb'
|
||||
|
||||
Reference in New Issue
Block a user