Merge remote-tracking branch 'origin' into xrpc-rework

This commit is contained in:
Hailey
2024-07-30 23:40:50 -07:00
168 changed files with 13414 additions and 11113 deletions
+70 -61
View File
@@ -14,29 +14,33 @@ export type FeedTunerFn = (
slices: 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 {
_reactKey: string
isFlattenedReply = false
_feedPost: FeedViewPost
items: FeedSliceItem[]
constructor(public items: FeedViewPost[]) {
const item = items[0]
this._reactKey = `slice-${item.post.uri}-${
item.reason?.indexedAt || item.post.indexedAt
constructor(feedPost: FeedViewPost) {
this._feedPost = feedPost
this._reactKey = `slice-${feedPost.post.uri}-${
feedPost.reason?.indexedAt || feedPost.post.indexedAt
}`
this.items = [toSliceItem(feedPost)]
}
get uri() {
if (this.isFlattenedReply) {
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
return this._feedPost.post.uri
}
get isThread() {
@@ -48,31 +52,42 @@ export class FeedViewPostsSlice {
)
}
get isFullThread() {
return this.isThread && !this.items[0].reply
}
get rootItem() {
if (this.isFlattenedReply) {
return this.items[1]
}
return this.items[0]
get isQuotePost() {
const embed = this._feedPost.post.embed
return (
AppBskyEmbedRecord.isView(embed) ||
AppBskyEmbedRecordWithMedia.isView(embed)
)
}
get isReply() {
return (
AppBskyFeedPost.isRecord(this.rootItem.post.record) &&
!!this.rootItem.post.record.reply
AppBskyFeedPost.isRecord(this._feedPost.post.record) &&
!!this._feedPost.post.record.reply
)
}
get source(): ReasonFeedSource | undefined {
return this.items.find(item => '__source' in item && !!item.__source)
?.__source as ReasonFeedSource
get reason() {
return '__source' in this._feedPost
? (this._feedPost.__source as ReasonFeedSource)
: this._feedPost.reason
}
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) {
@@ -97,24 +112,24 @@ export class FeedViewPostsSlice {
if (this.items[0].reply) {
const reply = this.items[0].reply
if (AppBskyFeedDefs.isPostView(reply.parent)) {
this.isFlattenedReply = true
this.items.splice(0, 0, {post: reply.parent})
}
}
}
isFollowingAllAuthors(userDid: string) {
const item = this.rootItem
if (item.post.author.did === userDid) {
const feedPost = this._feedPost
if (feedPost.post.author.did === userDid) {
return true
}
if (AppBskyFeedDefs.isPostView(item.reply?.parent)) {
const parent = item.reply?.parent
if (AppBskyFeedDefs.isPostView(feedPost.reply?.parent)) {
const parent = feedPost.reply?.parent
if (parent?.author.did === userDid) {
return true
}
return (
parent?.author.viewer?.following && item.post.author.viewer?.following
parent?.author.viewer?.following &&
feedPost.post.author.viewer?.following
)
}
return false
@@ -127,7 +142,7 @@ export class NoopFeedTuner {
feed: FeedViewPost[],
_opts?: {dryRun: boolean; maintainOrder: boolean},
): FeedViewPostsSlice[] {
return feed.map(item => new FeedViewPostsSlice([item]))
return feed.map(item => new FeedViewPostsSlice(item))
}
}
@@ -165,7 +180,7 @@ export class FeedTuner {
})
if (maintainOrder) {
slices = feed.map(item => new FeedViewPostsSlice([item]))
slices = feed.map(item => new FeedViewPostsSlice(item))
} else {
// arrange the posts into thread slices
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
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
if (
AppBskyFeedDefs.isPostView(reply.parent) &&
@@ -256,8 +271,7 @@ export class FeedTuner {
static removeReposts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
for (let i = slices.length - 1; i >= 0; i--) {
const reason = slices[i].rootItem.reason
if (AppBskyFeedDefs.isReasonRepost(reason)) {
if (slices[i].isRepost) {
slices.splice(i, 1)
}
}
@@ -266,11 +280,7 @@ export class FeedTuner {
static removeQuotePosts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
for (let i = slices.length - 1; i >= 0; i--) {
const embed = slices[i].rootItem.post.embed
if (
AppBskyEmbedRecord.isView(embed) ||
AppBskyEmbedRecordWithMedia.isView(embed)
) {
if (slices[i].isQuotePost) {
slices.splice(i, 1)
}
}
@@ -315,19 +325,18 @@ export class FeedTuner {
// remove any replies without at least minLikes likes
for (let i = slices.length - 1; i >= 0; i--) {
const slice = slices[i]
if (slice.isFullThread || !slice.isReply) {
continue
}
const item = slice.rootItem
const isRepost = Boolean(item.reason)
if (isRepost) {
continue
}
if ((item.post.likeCount || 0) < minLikes) {
slices.splice(i, 1)
} else if (followedOnly && !slice.isFollowingAllAuthors(userDid)) {
slices.splice(i, 1)
if (slice.isReply) {
if (slice.isThread && slice.includesThreadRoot) {
continue
}
if (slice.isRepost) {
continue
}
if (slice.likeCount < minLikes) {
slices.splice(i, 1)
} else if (followedOnly && !slice.isFollowingAllAuthors(userDid)) {
slices.splice(i, 1)
}
}
}
return slices
+1 -1
View File
@@ -251,7 +251,7 @@ class MergeFeedSource_Following extends MergeFeedSource {
dryRun: false,
maintainOrder: true,
})
res.data.feed = slices.map(slice => slice.rootItem)
res.data.feed = slices.map(slice => slice._feedPost)
return res
}
}
+4
View File
@@ -37,6 +37,10 @@ interface PostOpts {
uri: string
cid: string
}
video?: {
uri: string
cid: string
}
extLink?: ExternalEmbedDraft
images?: ImageModel[]
labels?: string[]
+6 -4
View File
@@ -1,10 +1,10 @@
import {timeout} from './timeout'
export async function until(
export async function until<T>(
retries: number,
delay: number,
cond: (v: any, err: any) => boolean,
fn: () => Promise<any>,
cond: (v: T, err: any) => boolean,
fn: () => Promise<T>,
): Promise<boolean> {
while (retries > 0) {
try {
@@ -13,7 +13,9 @@ export async function until(
return true
}
} catch (e: any) {
if (cond(undefined, e)) {
// TODO: change the type signature of cond to accept undefined
// however this breaks every existing usage of until -sfn
if (cond(undefined as unknown as T, e)) {
return true
}
}
+2
View File
@@ -133,3 +133,5 @@ export const GIF_SEARCH = (params: string) =>
`${GIF_SERVICE}/tenor/v2/search?${params}`
export const GIF_FEATURED = (params: string) =>
`${GIF_SERVICE}/tenor/v2/featured?${params}`
export const MAX_LABELERS = 20
+2 -2
View File
@@ -3,7 +3,7 @@ import React from 'react'
export const useDedupe = () => {
const canDo = React.useRef(true)
return React.useRef((cb: () => unknown) => {
return React.useCallback((cb: () => unknown) => {
if (canDo.current) {
canDo.current = false
setTimeout(() => {
@@ -13,5 +13,5 @@ export const useDedupe = () => {
return true
}
return false
}).current
}, [])
}
+8 -9
View File
@@ -18,15 +18,14 @@ export function useIntentHandler() {
React.useEffect(() => {
const handleIncomingURL = (url: string) => {
Referrer.getReferrerInfoAsync().then(info => {
if (info && info.hostname !== 'bsky.app') {
logEvent('deepLink:referrerReceived', {
to: url,
referrer: info?.referrer,
hostname: info?.hostname,
})
}
})
const referrerInfo = Referrer.getReferrerInfo()
if (referrerInfo && referrerInfo.hostname !== 'bsky.app') {
logEvent('deepLink:referrerReceived', {
to: url,
referrer: referrerInfo?.referrer,
hostname: referrerInfo?.hostname,
})
}
// We want to be able to support bluesky:// deeplinks. It's unnatural for someone to use a deeplink with three
// slashes, like bluesky:///intent/follow. However, supporting just two slashes causes us to have to take care
+2 -2
View File
@@ -17,14 +17,14 @@ export async function openPicker(opts?: ImagePickerOptions) {
})
if (response.assets && response.assets.length > 4) {
Toast.show('You may only select up to 4 images')
Toast.show('You may only select up to 4 images', 'exclamation-circle')
}
return (response.assets ?? [])
.slice(0, 4)
.filter(asset => {
if (asset.mimeType?.startsWith('image/')) return true
Toast.show('Only image files are supported')
Toast.show('Only image files are supported', 'exclamation-circle')
return false
})
.map(image => ({
+36
View File
@@ -0,0 +1,36 @@
/**
* TEMPORARY: THIS IS A TEMPORARY PLACEHOLDER. THAT MEANS IT IS TEMPORARY. I.E. WILL BE REMOVED. NOT TO USE IN PRODUCTION.
* @temporary
* PS: This is a temporary placeholder for the video types. It will be removed once the actual types are implemented.
* Not joking, this is temporary.
*/
export interface JobStatus {
jobId: string
did: string
cid: string
state: JobState
progress?: number
errorHuman?: string
errorMachine?: string
}
export enum JobState {
JOB_STATE_UNSPECIFIED = 'JOB_STATE_UNSPECIFIED',
JOB_STATE_CREATED = 'JOB_STATE_CREATED',
JOB_STATE_ENCODING = 'JOB_STATE_ENCODING',
JOB_STATE_ENCODED = 'JOB_STATE_ENCODED',
JOB_STATE_UPLOADING = 'JOB_STATE_UPLOADING',
JOB_STATE_UPLOADED = 'JOB_STATE_UPLOADED',
JOB_STATE_CDN_PROCESSING = 'JOB_STATE_CDN_PROCESSING',
JOB_STATE_CDN_PROCESSED = 'JOB_STATE_CDN_PROCESSED',
JOB_STATE_FAILED = 'JOB_STATE_FAILED',
JOB_STATE_COMPLETED = 'JOB_STATE_COMPLETED',
}
export interface UploadVideoResponse {
job_id: string
did: string
cid: string
state: JobState
}
+6 -9
View File
@@ -42,14 +42,13 @@ export type CommonNavigatorParams = {
Hashtag: {tag: string; author?: string}
MessagesConversation: {conversation: string; embed?: string}
MessagesSettings: undefined
NotificationsSettings: undefined
Feeds: undefined
Start: {name: string; rkey: string}
StarterPack: {name: string; rkey: string; new?: boolean}
StarterPackShort: {code: string}
StarterPackWizard: undefined
StarterPackEdit: {
rkey?: string
}
StarterPackEdit: {rkey?: string}
}
export type BottomTabNavigatorParams = CommonNavigatorParams & {
@@ -69,7 +68,7 @@ export type SearchTabNavigatorParams = CommonNavigatorParams & {
}
export type NotificationsTabNavigatorParams = CommonNavigatorParams & {
Notifications: undefined
Notifications: {show?: 'all'}
}
export type MyProfileTabNavigatorParams = CommonNavigatorParams & {
@@ -84,7 +83,7 @@ export type FlatNavigatorParams = CommonNavigatorParams & {
Home: undefined
Search: {q?: string}
Feeds: undefined
Notifications: undefined
Notifications: {show?: 'all'}
Hashtag: {tag: string; author?: string}
Messages: {pushToConversation?: string; animation?: 'push' | 'pop'}
}
@@ -96,7 +95,7 @@ export type AllNavigatorParams = CommonNavigatorParams & {
Search: {q?: string}
Feeds: undefined
NotificationsTab: undefined
Notifications: undefined
Notifications: {show?: 'all'}
MyProfileTab: undefined
Hashtag: {tag: string; author?: string}
MessagesTab: undefined
@@ -105,9 +104,7 @@ export type AllNavigatorParams = CommonNavigatorParams & {
StarterPack: {name: string; rkey: string; new?: boolean}
StarterPackShort: {code: string}
StarterPackWizard: undefined
StarterPackEdit: {
rkey?: string
}
StarterPackEdit: {rkey?: string}
}
// NOTE
+1 -1
View File
@@ -20,6 +20,6 @@ export async function shareUrl(url: string) {
// React Native Share is not supported by web. Web Share API
// has increasing but not full support, so default to clipboard
setStringAsync(url)
Toast.show('Copied to clipboard')
Toast.show('Copied to clipboard', 'clipboard-check')
}
}
+1
View File
@@ -14,3 +14,4 @@ export type Gate =
| 'suggested_follows_interstitial'
| 'ungroup_follow_backs'
| 'videos'
| 'small_avi_thumb'