Lex refactor (#362)
* Remove the hackcheck for upgrades * Rename the PostEmbeds folder to match the codebase style * Updates to latest lex refactor * Update to use new bsky agent * Update to use api package's richtext library * Switch to upsertProfile * Add TextEncoder/TextDecoder polyfill * Add Intl.Segmenter polyfill * Update composer to calculate lengths by grapheme * Fix detox * Fix login in e2e * Create account e2e passing * Implement an e2e mocking framework * Don't use private methods on mobx models as mobx can't track them * Add tooling for e2e-specific builds and add e2e media-picker mock * Add some tests and fix some bugs around profile editing * Add shell tests * Add home screen tests * Add thread screen tests * Add tests for other user profile screens * Add search screen tests * Implement profile imagery change tools and tests * Update to new embed behaviors * Add post tests * Fix to profile-screen test * Fix session resumption * Update web composer to new api * 1.11.0 * Fix pagination cursor parameters * Add quote posts to notifications * Fix embed layouts * Remove youtube inline player and improve tap handling on link cards * Reset minimal shell mode on all screen loads and feed swipes (close #299) * Update podfile.lock * Improve post notfound UI (close #366) * Bump atproto packages
This commit is contained in:
parent
19f3a2fa92
commit
a3334a01a2
133 changed files with 3103 additions and 2839 deletions
2
src/state/models/cache/image-sizes.ts
vendored
2
src/state/models/cache/image-sizes.ts
vendored
|
@ -3,7 +3,7 @@ import {Dim} from 'lib/media/manip'
|
|||
|
||||
export class ImageSizesCache {
|
||||
sizes: Map<string, Dim> = new Map()
|
||||
private activeRequests: Map<string, Promise<Dim>> = new Map()
|
||||
activeRequests: Map<string, Promise<Dim>> = new Map()
|
||||
|
||||
constructor() {}
|
||||
|
||||
|
|
21
src/state/models/cache/my-follows.ts
vendored
21
src/state/models/cache/my-follows.ts
vendored
|
@ -1,15 +1,12 @@
|
|||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import {FollowRecord, AppBskyActorProfile, AppBskyActorRef} from '@atproto/api'
|
||||
import {FollowRecord, AppBskyActorDefs} from '@atproto/api'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
import {bundleAsync} from 'lib/async/bundle'
|
||||
|
||||
const CACHE_TTL = 1000 * 60 * 60 // hourly
|
||||
type FollowsListResponse = Awaited<ReturnType<FollowRecord['list']>>
|
||||
type FollowsListResponseRecord = FollowsListResponse['records'][0]
|
||||
type Profile =
|
||||
| AppBskyActorProfile.ViewBasic
|
||||
| AppBskyActorProfile.View
|
||||
| AppBskyActorRef.WithInfo
|
||||
type Profile = AppBskyActorDefs.ProfileViewBasic | AppBskyActorDefs.ProfileView
|
||||
|
||||
/**
|
||||
* This model is used to maintain a synced local cache of the user's
|
||||
|
@ -53,21 +50,21 @@ export class MyFollowsCache {
|
|||
|
||||
fetch = bundleAsync(async () => {
|
||||
this.rootStore.log.debug('MyFollowsModel:fetch running full fetch')
|
||||
let before
|
||||
let rkeyStart
|
||||
let records: FollowsListResponseRecord[] = []
|
||||
do {
|
||||
const res: FollowsListResponse =
|
||||
await this.rootStore.api.app.bsky.graph.follow.list({
|
||||
user: this.rootStore.me.did,
|
||||
before,
|
||||
await this.rootStore.agent.app.bsky.graph.follow.list({
|
||||
repo: this.rootStore.me.did,
|
||||
rkeyStart,
|
||||
})
|
||||
records = records.concat(res.records)
|
||||
before = res.cursor
|
||||
} while (typeof before !== 'undefined')
|
||||
rkeyStart = res.cursor
|
||||
} while (typeof rkeyStart !== 'undefined')
|
||||
runInAction(() => {
|
||||
this.followDidToRecordMap = {}
|
||||
for (const record of records) {
|
||||
this.followDidToRecordMap[record.value.subject.did] = record.uri
|
||||
this.followDidToRecordMap[record.value.subject] = record.uri
|
||||
}
|
||||
this.lastSync = Date.now()
|
||||
this.myDid = this.rootStore.me.did
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import {AppBskyActorProfile, AppBskyActorRef} from '@atproto/api'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import sampleSize from 'lodash.samplesize'
|
||||
import {bundleAsync} from 'lib/async/bundle'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
|
||||
export type RefWithInfoAndFollowers = AppBskyActorRef.WithInfo & {
|
||||
followers: AppBskyActorProfile.View[]
|
||||
export type RefWithInfoAndFollowers = AppBskyActorDefs.ProfileViewBasic & {
|
||||
followers: AppBskyActorDefs.ProfileView[]
|
||||
}
|
||||
|
||||
export type ProfileViewFollows = AppBskyActorProfile.View & {
|
||||
follows: AppBskyActorRef.WithInfo[]
|
||||
export type ProfileViewFollows = AppBskyActorDefs.ProfileView & {
|
||||
follows: AppBskyActorDefs.ProfileViewBasic[]
|
||||
}
|
||||
|
||||
export class FoafsModel {
|
||||
|
@ -51,14 +51,14 @@ export class FoafsModel {
|
|||
this.popular.length = 0
|
||||
|
||||
// fetch their profiles
|
||||
const profiles = await this.rootStore.api.app.bsky.actor.getProfiles({
|
||||
const profiles = await this.rootStore.agent.getProfiles({
|
||||
actors: this.sources,
|
||||
})
|
||||
|
||||
// fetch their follows
|
||||
const results = await Promise.allSettled(
|
||||
this.sources.map(source =>
|
||||
this.rootStore.api.app.bsky.graph.getFollows({user: source}),
|
||||
this.rootStore.agent.getFollows({actor: source}),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import {AppBskyActorProfile as Profile} from '@atproto/api'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import shuffle from 'lodash.shuffle'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
|
@ -8,7 +8,9 @@ import {SUGGESTED_FOLLOWS} from 'lib/constants'
|
|||
|
||||
const PAGE_SIZE = 30
|
||||
|
||||
export type SuggestedActor = Profile.ViewBasic | Profile.View
|
||||
export type SuggestedActor =
|
||||
| AppBskyActorDefs.ProfileViewBasic
|
||||
| AppBskyActorDefs.ProfileView
|
||||
|
||||
export class SuggestedActorsModel {
|
||||
// state
|
||||
|
@ -20,7 +22,7 @@ export class SuggestedActorsModel {
|
|||
hasMore = true
|
||||
loadMoreCursor?: string
|
||||
|
||||
private hardCodedSuggestions: SuggestedActor[] | undefined
|
||||
hardCodedSuggestions: SuggestedActor[] | undefined
|
||||
|
||||
// data
|
||||
suggestions: SuggestedActor[] = []
|
||||
|
@ -82,7 +84,7 @@ export class SuggestedActorsModel {
|
|||
this.loadMoreCursor = undefined
|
||||
} else {
|
||||
// pull from the PDS' algo
|
||||
res = await this.rootStore.api.app.bsky.actor.getSuggestions({
|
||||
res = await this.rootStore.agent.app.bsky.actor.getSuggestions({
|
||||
limit: this.pageSize,
|
||||
cursor: this.loadMoreCursor,
|
||||
})
|
||||
|
@ -104,7 +106,7 @@ export class SuggestedActorsModel {
|
|||
}
|
||||
})
|
||||
|
||||
private async fetchHardcodedSuggestions() {
|
||||
async fetchHardcodedSuggestions() {
|
||||
if (this.hardCodedSuggestions) {
|
||||
return
|
||||
}
|
||||
|
@ -118,9 +120,9 @@ export class SuggestedActorsModel {
|
|||
]
|
||||
|
||||
// fetch the profiles in chunks of 25 (the limit allowed by `getProfiles`)
|
||||
let profiles: Profile.View[] = []
|
||||
let profiles: AppBskyActorDefs.ProfileView[] = []
|
||||
do {
|
||||
const res = await this.rootStore.api.app.bsky.actor.getProfiles({
|
||||
const res = await this.rootStore.agent.getProfiles({
|
||||
actors: actors.splice(0, 25),
|
||||
})
|
||||
profiles = profiles.concat(res.data.profiles)
|
||||
|
@ -152,13 +154,13 @@ export class SuggestedActorsModel {
|
|||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading(isRefreshing = false) {
|
||||
_xLoading(isRefreshing = false) {
|
||||
this.isLoading = true
|
||||
this.isRefreshing = isRefreshing
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
private _xIdle(err?: any) {
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.isRefreshing = false
|
||||
this.hasLoaded = true
|
||||
|
|
|
@ -1,32 +1,29 @@
|
|||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import {
|
||||
AppBskyFeedGetTimeline as GetTimeline,
|
||||
AppBskyFeedFeedViewPost,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AppBskyFeedGetAuthorFeed as GetAuthorFeed,
|
||||
RichText,
|
||||
} from '@atproto/api'
|
||||
import AwaitLock from 'await-lock'
|
||||
import {bundleAsync} from 'lib/async/bundle'
|
||||
import sampleSize from 'lodash.samplesize'
|
||||
type FeedViewPost = AppBskyFeedFeedViewPost.Main
|
||||
type ReasonRepost = AppBskyFeedFeedViewPost.ReasonRepost
|
||||
type PostView = AppBskyFeedPost.View
|
||||
import {AtUri} from '../../third-party/uri'
|
||||
import {RootStoreModel} from './root-store'
|
||||
import * as apilib from 'lib/api/index'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {RichText} from 'lib/strings/rich-text'
|
||||
import {SUGGESTED_FOLLOWS} from 'lib/constants'
|
||||
import {
|
||||
getCombinedCursors,
|
||||
getMultipleAuthorsPosts,
|
||||
mergePosts,
|
||||
} from 'lib/api/build-suggested-posts'
|
||||
|
||||
import {FeedTuner, FeedViewPostsSlice} from 'lib/api/feed-manip'
|
||||
|
||||
const PAGE_SIZE = 30
|
||||
type FeedViewPost = AppBskyFeedDefs.FeedViewPost
|
||||
type ReasonRepost = AppBskyFeedDefs.ReasonRepost
|
||||
type PostView = AppBskyFeedDefs.PostView
|
||||
|
||||
const PAGE_SIZE = 30
|
||||
let _idCounter = 0
|
||||
|
||||
export class FeedItemModel {
|
||||
|
@ -51,11 +48,7 @@ export class FeedItemModel {
|
|||
const valid = AppBskyFeedPost.validateRecord(this.post.record)
|
||||
if (valid.success) {
|
||||
this.postRecord = this.post.record
|
||||
this.richText = new RichText(
|
||||
this.postRecord.text,
|
||||
this.postRecord.entities,
|
||||
{cleanNewlines: true},
|
||||
)
|
||||
this.richText = new RichText(this.postRecord, {cleanNewlines: true})
|
||||
} else {
|
||||
rootStore.log.warn(
|
||||
'Received an invalid app.bsky.feed.post record',
|
||||
|
@ -82,7 +75,7 @@ export class FeedItemModel {
|
|||
copyMetrics(v: FeedViewPost) {
|
||||
this.post.replyCount = v.post.replyCount
|
||||
this.post.repostCount = v.post.repostCount
|
||||
this.post.upvoteCount = v.post.upvoteCount
|
||||
this.post.likeCount = v.post.likeCount
|
||||
this.post.viewer = v.post.viewer
|
||||
}
|
||||
|
||||
|
@ -92,68 +85,43 @@ export class FeedItemModel {
|
|||
}
|
||||
}
|
||||
|
||||
async toggleUpvote() {
|
||||
const wasUpvoted = !!this.post.viewer.upvote
|
||||
const wasDownvoted = !!this.post.viewer.downvote
|
||||
const res = await this.rootStore.api.app.bsky.feed.setVote({
|
||||
subject: {
|
||||
uri: this.post.uri,
|
||||
cid: this.post.cid,
|
||||
},
|
||||
direction: wasUpvoted ? 'none' : 'up',
|
||||
})
|
||||
runInAction(() => {
|
||||
if (wasDownvoted) {
|
||||
this.post.downvoteCount--
|
||||
}
|
||||
if (wasUpvoted) {
|
||||
this.post.upvoteCount--
|
||||
} else {
|
||||
this.post.upvoteCount++
|
||||
}
|
||||
this.post.viewer.upvote = res.data.upvote
|
||||
this.post.viewer.downvote = res.data.downvote
|
||||
})
|
||||
}
|
||||
|
||||
async toggleDownvote() {
|
||||
const wasUpvoted = !!this.post.viewer.upvote
|
||||
const wasDownvoted = !!this.post.viewer.downvote
|
||||
const res = await this.rootStore.api.app.bsky.feed.setVote({
|
||||
subject: {
|
||||
uri: this.post.uri,
|
||||
cid: this.post.cid,
|
||||
},
|
||||
direction: wasDownvoted ? 'none' : 'down',
|
||||
})
|
||||
runInAction(() => {
|
||||
if (wasUpvoted) {
|
||||
this.post.upvoteCount--
|
||||
}
|
||||
if (wasDownvoted) {
|
||||
this.post.downvoteCount--
|
||||
} else {
|
||||
this.post.downvoteCount++
|
||||
}
|
||||
this.post.viewer.upvote = res.data.upvote
|
||||
this.post.viewer.downvote = res.data.downvote
|
||||
})
|
||||
async toggleLike() {
|
||||
if (this.post.viewer?.like) {
|
||||
await this.rootStore.agent.deleteLike(this.post.viewer.like)
|
||||
runInAction(() => {
|
||||
this.post.likeCount = this.post.likeCount || 0
|
||||
this.post.viewer = this.post.viewer || {}
|
||||
this.post.likeCount--
|
||||
this.post.viewer.like = undefined
|
||||
})
|
||||
} else {
|
||||
const res = await this.rootStore.agent.like(this.post.uri, this.post.cid)
|
||||
runInAction(() => {
|
||||
this.post.likeCount = this.post.likeCount || 0
|
||||
this.post.viewer = this.post.viewer || {}
|
||||
this.post.likeCount++
|
||||
this.post.viewer.like = res.uri
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async toggleRepost() {
|
||||
if (this.post.viewer.repost) {
|
||||
await apilib.unrepost(this.rootStore, this.post.viewer.repost)
|
||||
if (this.post.viewer?.repost) {
|
||||
await this.rootStore.agent.deleteRepost(this.post.viewer.repost)
|
||||
runInAction(() => {
|
||||
this.post.repostCount = this.post.repostCount || 0
|
||||
this.post.viewer = this.post.viewer || {}
|
||||
this.post.repostCount--
|
||||
this.post.viewer.repost = undefined
|
||||
})
|
||||
} else {
|
||||
const res = await apilib.repost(
|
||||
this.rootStore,
|
||||
const res = await this.rootStore.agent.repost(
|
||||
this.post.uri,
|
||||
this.post.cid,
|
||||
)
|
||||
runInAction(() => {
|
||||
this.post.repostCount = this.post.repostCount || 0
|
||||
this.post.viewer = this.post.viewer || {}
|
||||
this.post.repostCount++
|
||||
this.post.viewer.repost = res.uri
|
||||
})
|
||||
|
@ -161,10 +129,7 @@ export class FeedItemModel {
|
|||
}
|
||||
|
||||
async delete() {
|
||||
await this.rootStore.api.app.bsky.feed.post.delete({
|
||||
did: this.post.author.did,
|
||||
rkey: new AtUri(this.post.uri).rkey,
|
||||
})
|
||||
await this.rootStore.agent.deletePost(this.post.uri)
|
||||
this.rootStore.emitPostDeleted(this.post.uri)
|
||||
}
|
||||
}
|
||||
|
@ -250,7 +215,7 @@ export class FeedModel {
|
|||
tuner = new FeedTuner()
|
||||
|
||||
// used to linearize async modifications to state
|
||||
private lock = new AwaitLock()
|
||||
lock = new AwaitLock()
|
||||
|
||||
// data
|
||||
slices: FeedSliceModel[] = []
|
||||
|
@ -291,8 +256,8 @@ export class FeedModel {
|
|||
const params = this.params as GetAuthorFeed.QueryParams
|
||||
const item = slice.rootItem
|
||||
const isRepost =
|
||||
item?.reasonRepost?.by?.handle === params.author ||
|
||||
item?.reasonRepost?.by?.did === params.author
|
||||
item?.reasonRepost?.by?.handle === params.actor ||
|
||||
item?.reasonRepost?.by?.did === params.actor
|
||||
return (
|
||||
!item.reply || // not a reply
|
||||
isRepost || // but allow if it's a repost
|
||||
|
@ -338,7 +303,7 @@ export class FeedModel {
|
|||
return this.setup()
|
||||
}
|
||||
|
||||
private get feedTuners() {
|
||||
get feedTuners() {
|
||||
if (this.feedType === 'goodstuff') {
|
||||
return [
|
||||
FeedTuner.dedupReposts,
|
||||
|
@ -406,7 +371,7 @@ export class FeedModel {
|
|||
this._xLoading()
|
||||
try {
|
||||
const res = await this._getFeed({
|
||||
before: this.loadMoreCursor,
|
||||
cursor: this.loadMoreCursor,
|
||||
limit: PAGE_SIZE,
|
||||
})
|
||||
await this._appendAll(res)
|
||||
|
@ -439,7 +404,7 @@ export class FeedModel {
|
|||
try {
|
||||
do {
|
||||
const res: GetTimeline.Response = await this._getFeed({
|
||||
before: cursor,
|
||||
cursor,
|
||||
limit: Math.min(numToFetch, 100),
|
||||
})
|
||||
if (res.data.feed.length === 0) {
|
||||
|
@ -478,14 +443,18 @@ export class FeedModel {
|
|||
new FeedSliceModel(this.rootStore, `item-${_idCounter++}`, slice),
|
||||
)
|
||||
if (autoPrepend) {
|
||||
this.slices = nextSlicesModels.concat(
|
||||
this.slices.filter(slice1 =>
|
||||
nextSlicesModels.find(slice2 => slice1.uri === slice2.uri),
|
||||
),
|
||||
)
|
||||
this.setHasNewLatest(false)
|
||||
runInAction(() => {
|
||||
this.slices = nextSlicesModels.concat(
|
||||
this.slices.filter(slice1 =>
|
||||
nextSlicesModels.find(slice2 => slice1.uri === slice2.uri),
|
||||
),
|
||||
)
|
||||
this.setHasNewLatest(false)
|
||||
})
|
||||
} else {
|
||||
this.nextSlices = nextSlicesModels
|
||||
runInAction(() => {
|
||||
this.nextSlices = nextSlicesModels
|
||||
})
|
||||
this.setHasNewLatest(true)
|
||||
}
|
||||
} else {
|
||||
|
@ -519,13 +488,13 @@ export class FeedModel {
|
|||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading(isRefreshing = false) {
|
||||
_xLoading(isRefreshing = false) {
|
||||
this.isLoading = true
|
||||
this.isRefreshing = isRefreshing
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
private _xIdle(err?: any) {
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.isRefreshing = false
|
||||
this.hasLoaded = true
|
||||
|
@ -538,14 +507,12 @@ export class FeedModel {
|
|||
// helper functions
|
||||
// =
|
||||
|
||||
private async _replaceAll(
|
||||
res: GetTimeline.Response | GetAuthorFeed.Response,
|
||||
) {
|
||||
async _replaceAll(res: GetTimeline.Response | GetAuthorFeed.Response) {
|
||||
this.pollCursor = res.data.feed[0]?.post.uri
|
||||
return this._appendAll(res, true)
|
||||
}
|
||||
|
||||
private async _appendAll(
|
||||
async _appendAll(
|
||||
res: GetTimeline.Response | GetAuthorFeed.Response,
|
||||
replace = false,
|
||||
) {
|
||||
|
@ -572,7 +539,7 @@ export class FeedModel {
|
|||
})
|
||||
}
|
||||
|
||||
private _updateAll(res: GetTimeline.Response | GetAuthorFeed.Response) {
|
||||
_updateAll(res: GetTimeline.Response | GetAuthorFeed.Response) {
|
||||
for (const item of res.data.feed) {
|
||||
const existingSlice = this.slices.find(slice =>
|
||||
slice.containsUri(item.post.uri),
|
||||
|
@ -596,7 +563,7 @@ export class FeedModel {
|
|||
const responses = await getMultipleAuthorsPosts(
|
||||
this.rootStore,
|
||||
sampleSize(SUGGESTED_FOLLOWS(String(this.rootStore.agent.service)), 20),
|
||||
params.before,
|
||||
params.cursor,
|
||||
20,
|
||||
)
|
||||
const combinedCursor = getCombinedCursors(responses)
|
||||
|
@ -611,9 +578,7 @@ export class FeedModel {
|
|||
headers: lastHeaders,
|
||||
}
|
||||
} else if (this.feedType === 'home') {
|
||||
return this.rootStore.api.app.bsky.feed.getTimeline(
|
||||
params as GetTimeline.QueryParams,
|
||||
)
|
||||
return this.rootStore.agent.getTimeline(params as GetTimeline.QueryParams)
|
||||
} else if (this.feedType === 'goodstuff') {
|
||||
const res = await getGoodStuff(
|
||||
this.rootStore.session.currentSession?.accessJwt || '',
|
||||
|
@ -624,7 +589,7 @@ export class FeedModel {
|
|||
)
|
||||
return res
|
||||
} else {
|
||||
return this.rootStore.api.app.bsky.feed.getAuthorFeed(
|
||||
return this.rootStore.agent.getAuthorFeed(
|
||||
params as GetAuthorFeed.QueryParams,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import {AtUri} from '../../third-party/uri'
|
||||
import {AppBskyFeedGetVotes as GetVotes} from '@atproto/api'
|
||||
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
|
||||
import {RootStoreModel} from './root-store'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {bundleAsync} from 'lib/async/bundle'
|
||||
|
@ -8,24 +8,24 @@ import * as apilib from 'lib/api/index'
|
|||
|
||||
const PAGE_SIZE = 30
|
||||
|
||||
export type VoteItem = GetVotes.Vote
|
||||
export type LikeItem = GetLikes.Like
|
||||
|
||||
export class VotesViewModel {
|
||||
export class LikesViewModel {
|
||||
// state
|
||||
isLoading = false
|
||||
isRefreshing = false
|
||||
hasLoaded = false
|
||||
error = ''
|
||||
resolvedUri = ''
|
||||
params: GetVotes.QueryParams
|
||||
params: GetLikes.QueryParams
|
||||
hasMore = true
|
||||
loadMoreCursor?: string
|
||||
|
||||
// data
|
||||
uri: string = ''
|
||||
votes: VoteItem[] = []
|
||||
likes: LikeItem[] = []
|
||||
|
||||
constructor(public rootStore: RootStoreModel, params: GetVotes.QueryParams) {
|
||||
constructor(public rootStore: RootStoreModel, params: GetLikes.QueryParams) {
|
||||
makeAutoObservable(
|
||||
this,
|
||||
{
|
||||
|
@ -68,9 +68,9 @@ export class VotesViewModel {
|
|||
const params = Object.assign({}, this.params, {
|
||||
uri: this.resolvedUri,
|
||||
limit: PAGE_SIZE,
|
||||
before: replace ? undefined : this.loadMoreCursor,
|
||||
cursor: replace ? undefined : this.loadMoreCursor,
|
||||
})
|
||||
const res = await this.rootStore.api.app.bsky.feed.getVotes(params)
|
||||
const res = await this.rootStore.agent.getLikes(params)
|
||||
if (replace) {
|
||||
this._replaceAll(res)
|
||||
} else {
|
||||
|
@ -85,13 +85,13 @@ export class VotesViewModel {
|
|||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading(isRefreshing = false) {
|
||||
_xLoading(isRefreshing = false) {
|
||||
this.isLoading = true
|
||||
this.isRefreshing = isRefreshing
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
private _xIdle(err?: any) {
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.isRefreshing = false
|
||||
this.hasLoaded = true
|
||||
|
@ -104,7 +104,7 @@ export class VotesViewModel {
|
|||
// helper functions
|
||||
// =
|
||||
|
||||
private async _resolveUri() {
|
||||
async _resolveUri() {
|
||||
const urip = new AtUri(this.params.uri)
|
||||
if (!urip.host.startsWith('did:')) {
|
||||
try {
|
||||
|
@ -118,14 +118,14 @@ export class VotesViewModel {
|
|||
})
|
||||
}
|
||||
|
||||
private _replaceAll(res: GetVotes.Response) {
|
||||
this.votes = []
|
||||
_replaceAll(res: GetLikes.Response) {
|
||||
this.likes = []
|
||||
this._appendAll(res)
|
||||
}
|
||||
|
||||
private _appendAll(res: GetVotes.Response) {
|
||||
_appendAll(res: GetLikes.Response) {
|
||||
this.loadMoreCursor = res.data.cursor
|
||||
this.hasMore = !!this.loadMoreCursor
|
||||
this.votes = this.votes.concat(res.data.votes)
|
||||
this.likes = this.likes.concat(res.data.likes)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import {makeAutoObservable} from 'mobx'
|
||||
import {XRPCError, XRPCInvalidResponseError} from '@atproto/xrpc'
|
||||
// import {XRPCError, XRPCInvalidResponseError} from '@atproto/xrpc' TODO
|
||||
|
||||
const MAX_ENTRIES = 300
|
||||
|
||||
|
@ -32,7 +32,7 @@ export class LogModel {
|
|||
makeAutoObservable(this)
|
||||
}
|
||||
|
||||
private add(entry: LogEntry) {
|
||||
add(entry: LogEntry) {
|
||||
this.entries.push(entry)
|
||||
while (this.entries.length > MAX_ENTRIES) {
|
||||
this.entries = this.entries.slice(50)
|
||||
|
@ -79,14 +79,14 @@ export class LogModel {
|
|||
function detailsToStr(details?: any) {
|
||||
if (details && typeof details !== 'string') {
|
||||
if (
|
||||
details instanceof XRPCInvalidResponseError ||
|
||||
// details instanceof XRPCInvalidResponseError || TODO
|
||||
details.constructor.name === 'XRPCInvalidResponseError'
|
||||
) {
|
||||
return `The server gave an ill-formatted response.\nMethod: ${
|
||||
details.lexiconNsid
|
||||
}.\nError: ${details.validationError.toString()}`
|
||||
} else if (
|
||||
details instanceof XRPCError ||
|
||||
// details instanceof XRPCError || TODO
|
||||
details.constructor.name === 'XRPCError'
|
||||
) {
|
||||
return `An XRPC error occurred.\nStatus: ${details.status}\nError: ${details.error}\nMessage: ${details.message}`
|
||||
|
|
|
@ -85,7 +85,7 @@ export class MeModel {
|
|||
if (sess.hasSession) {
|
||||
this.did = sess.currentSession?.did || ''
|
||||
this.handle = sess.currentSession?.handle || ''
|
||||
const profile = await this.rootStore.api.app.bsky.actor.getProfile({
|
||||
const profile = await this.rootStore.agent.getProfile({
|
||||
actor: this.did,
|
||||
})
|
||||
runInAction(() => {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import {
|
||||
AppBskyNotificationList as ListNotifications,
|
||||
AppBskyActorRef as ActorRef,
|
||||
AppBskyNotificationListNotifications as ListNotifications,
|
||||
AppBskyActorDefs,
|
||||
AppBskyFeedPost,
|
||||
AppBskyFeedRepost,
|
||||
AppBskyFeedVote,
|
||||
AppBskyGraphAssertion,
|
||||
AppBskyFeedLike,
|
||||
AppBskyGraphFollow,
|
||||
} from '@atproto/api'
|
||||
import AwaitLock from 'await-lock'
|
||||
|
@ -28,8 +27,7 @@ export interface GroupedNotification extends ListNotifications.Notification {
|
|||
type SupportedRecord =
|
||||
| AppBskyFeedPost.Record
|
||||
| AppBskyFeedRepost.Record
|
||||
| AppBskyFeedVote.Record
|
||||
| AppBskyGraphAssertion.Record
|
||||
| AppBskyFeedLike.Record
|
||||
| AppBskyGraphFollow.Record
|
||||
|
||||
export class NotificationsViewItemModel {
|
||||
|
@ -39,11 +37,10 @@ export class NotificationsViewItemModel {
|
|||
// data
|
||||
uri: string = ''
|
||||
cid: string = ''
|
||||
author: ActorRef.WithInfo = {
|
||||
author: AppBskyActorDefs.ProfileViewBasic = {
|
||||
did: '',
|
||||
handle: '',
|
||||
avatar: '',
|
||||
declaration: {cid: '', actorType: ''},
|
||||
}
|
||||
reason: string = ''
|
||||
reasonSubject?: string
|
||||
|
@ -86,8 +83,8 @@ export class NotificationsViewItemModel {
|
|||
}
|
||||
}
|
||||
|
||||
get isUpvote() {
|
||||
return this.reason === 'vote'
|
||||
get isLike() {
|
||||
return this.reason === 'like'
|
||||
}
|
||||
|
||||
get isRepost() {
|
||||
|
@ -102,16 +99,22 @@ export class NotificationsViewItemModel {
|
|||
return this.reason === 'reply'
|
||||
}
|
||||
|
||||
get isQuote() {
|
||||
return this.reason === 'quote'
|
||||
}
|
||||
|
||||
get isFollow() {
|
||||
return this.reason === 'follow'
|
||||
}
|
||||
|
||||
get isAssertion() {
|
||||
return this.reason === 'assertion'
|
||||
}
|
||||
|
||||
get needsAdditionalData() {
|
||||
if (this.isUpvote || this.isRepost || this.isReply || this.isMention) {
|
||||
if (
|
||||
this.isLike ||
|
||||
this.isRepost ||
|
||||
this.isReply ||
|
||||
this.isQuote ||
|
||||
this.isMention
|
||||
) {
|
||||
return !this.additionalPost
|
||||
}
|
||||
return false
|
||||
|
@ -124,7 +127,7 @@ export class NotificationsViewItemModel {
|
|||
const record = this.record
|
||||
if (
|
||||
AppBskyFeedRepost.isRecord(record) ||
|
||||
AppBskyFeedVote.isRecord(record)
|
||||
AppBskyFeedLike.isRecord(record)
|
||||
) {
|
||||
return record.subject.uri
|
||||
}
|
||||
|
@ -135,8 +138,7 @@ export class NotificationsViewItemModel {
|
|||
for (const ns of [
|
||||
AppBskyFeedPost,
|
||||
AppBskyFeedRepost,
|
||||
AppBskyFeedVote,
|
||||
AppBskyGraphAssertion,
|
||||
AppBskyFeedLike,
|
||||
AppBskyGraphFollow,
|
||||
]) {
|
||||
if (ns.isRecord(v)) {
|
||||
|
@ -163,9 +165,9 @@ export class NotificationsViewItemModel {
|
|||
return
|
||||
}
|
||||
let postUri
|
||||
if (this.isReply || this.isMention) {
|
||||
if (this.isReply || this.isQuote || this.isMention) {
|
||||
postUri = this.uri
|
||||
} else if (this.isUpvote || this.isRepost) {
|
||||
} else if (this.isLike || this.isRepost) {
|
||||
postUri = this.subjectUri
|
||||
}
|
||||
if (postUri) {
|
||||
|
@ -194,7 +196,7 @@ export class NotificationsViewModel {
|
|||
loadMoreCursor?: string
|
||||
|
||||
// used to linearize async modifications to state
|
||||
private lock = new AwaitLock()
|
||||
lock = new AwaitLock()
|
||||
|
||||
// data
|
||||
notifications: NotificationsViewItemModel[] = []
|
||||
|
@ -266,7 +268,7 @@ export class NotificationsViewModel {
|
|||
const params = Object.assign({}, this.params, {
|
||||
limit: PAGE_SIZE,
|
||||
})
|
||||
const res = await this.rootStore.api.app.bsky.notification.list(params)
|
||||
const res = await this.rootStore.agent.listNotifications(params)
|
||||
await this._replaceAll(res)
|
||||
this._xIdle()
|
||||
} catch (e: any) {
|
||||
|
@ -297,9 +299,9 @@ export class NotificationsViewModel {
|
|||
try {
|
||||
const params = Object.assign({}, this.params, {
|
||||
limit: PAGE_SIZE,
|
||||
before: this.loadMoreCursor,
|
||||
cursor: this.loadMoreCursor,
|
||||
})
|
||||
const res = await this.rootStore.api.app.bsky.notification.list(params)
|
||||
const res = await this.rootStore.agent.listNotifications(params)
|
||||
await this._appendAll(res)
|
||||
this._xIdle()
|
||||
} catch (e: any) {
|
||||
|
@ -325,7 +327,7 @@ export class NotificationsViewModel {
|
|||
try {
|
||||
this._xLoading()
|
||||
try {
|
||||
const res = await this.rootStore.api.app.bsky.notification.list({
|
||||
const res = await this.rootStore.agent.listNotifications({
|
||||
limit: PAGE_SIZE,
|
||||
})
|
||||
await this._prependAll(res)
|
||||
|
@ -357,8 +359,8 @@ export class NotificationsViewModel {
|
|||
try {
|
||||
do {
|
||||
const res: ListNotifications.Response =
|
||||
await this.rootStore.api.app.bsky.notification.list({
|
||||
before: cursor,
|
||||
await this.rootStore.agent.listNotifications({
|
||||
cursor,
|
||||
limit: Math.min(numToFetch, 100),
|
||||
})
|
||||
if (res.data.notifications.length === 0) {
|
||||
|
@ -390,7 +392,7 @@ export class NotificationsViewModel {
|
|||
*/
|
||||
loadUnreadCount = bundleAsync(async () => {
|
||||
const old = this.unreadCount
|
||||
const res = await this.rootStore.api.app.bsky.notification.getCount()
|
||||
const res = await this.rootStore.agent.countUnreadNotifications()
|
||||
runInAction(() => {
|
||||
this.unreadCount = res.data.count
|
||||
})
|
||||
|
@ -408,9 +410,7 @@ export class NotificationsViewModel {
|
|||
for (const notif of this.notifications) {
|
||||
notif.isRead = true
|
||||
}
|
||||
await this.rootStore.api.app.bsky.notification.updateSeen({
|
||||
seenAt: new Date().toISOString(),
|
||||
})
|
||||
await this.rootStore.agent.updateSeenNotifications()
|
||||
} catch (e: any) {
|
||||
this.rootStore.log.warn('Failed to update notifications read state', e)
|
||||
}
|
||||
|
@ -418,7 +418,7 @@ export class NotificationsViewModel {
|
|||
|
||||
async getNewMostRecent(): Promise<NotificationsViewItemModel | undefined> {
|
||||
let old = this.mostRecentNotificationUri
|
||||
const res = await this.rootStore.api.app.bsky.notification.list({
|
||||
const res = await this.rootStore.agent.listNotifications({
|
||||
limit: 1,
|
||||
})
|
||||
if (!res.data.notifications[0] || old === res.data.notifications[0].uri) {
|
||||
|
@ -437,13 +437,13 @@ export class NotificationsViewModel {
|
|||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading(isRefreshing = false) {
|
||||
_xLoading(isRefreshing = false) {
|
||||
this.isLoading = true
|
||||
this.isRefreshing = isRefreshing
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
private _xIdle(err?: any) {
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.isRefreshing = false
|
||||
this.hasLoaded = true
|
||||
|
@ -456,14 +456,14 @@ export class NotificationsViewModel {
|
|||
// helper functions
|
||||
// =
|
||||
|
||||
private async _replaceAll(res: ListNotifications.Response) {
|
||||
async _replaceAll(res: ListNotifications.Response) {
|
||||
if (res.data.notifications[0]) {
|
||||
this.mostRecentNotificationUri = res.data.notifications[0].uri
|
||||
}
|
||||
return this._appendAll(res, true)
|
||||
}
|
||||
|
||||
private async _appendAll(res: ListNotifications.Response, replace = false) {
|
||||
async _appendAll(res: ListNotifications.Response, replace = false) {
|
||||
this.loadMoreCursor = res.data.cursor
|
||||
this.hasMore = !!this.loadMoreCursor
|
||||
const promises = []
|
||||
|
@ -494,7 +494,7 @@ export class NotificationsViewModel {
|
|||
})
|
||||
}
|
||||
|
||||
private async _prependAll(res: ListNotifications.Response) {
|
||||
async _prependAll(res: ListNotifications.Response) {
|
||||
const promises = []
|
||||
const itemModels: NotificationsViewItemModel[] = []
|
||||
const dedupedNotifs = res.data.notifications.filter(
|
||||
|
@ -525,7 +525,7 @@ export class NotificationsViewModel {
|
|||
})
|
||||
}
|
||||
|
||||
private _updateAll(res: ListNotifications.Response) {
|
||||
_updateAll(res: ListNotifications.Response) {
|
||||
for (const item of res.data.notifications) {
|
||||
const existingItem = this.notifications.find(item2 => isEq(item, item2))
|
||||
if (existingItem) {
|
||||
|
|
|
@ -2,12 +2,13 @@ import {makeAutoObservable, runInAction} from 'mobx'
|
|||
import {
|
||||
AppBskyFeedGetPostThread as GetPostThread,
|
||||
AppBskyFeedPost as FeedPost,
|
||||
AppBskyFeedDefs,
|
||||
RichText,
|
||||
} from '@atproto/api'
|
||||
import {AtUri} from '../../third-party/uri'
|
||||
import {RootStoreModel} from './root-store'
|
||||
import * as apilib from 'lib/api/index'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {RichText} from 'lib/strings/rich-text'
|
||||
|
||||
function* reactKeyGenerator(): Generator<string> {
|
||||
let counter = 0
|
||||
|
@ -26,10 +27,10 @@ export class PostThreadViewPostModel {
|
|||
_hasMore = false
|
||||
|
||||
// data
|
||||
post: FeedPost.View
|
||||
post: AppBskyFeedDefs.PostView
|
||||
postRecord?: FeedPost.Record
|
||||
parent?: PostThreadViewPostModel | GetPostThread.NotFoundPost
|
||||
replies?: (PostThreadViewPostModel | GetPostThread.NotFoundPost)[]
|
||||
parent?: PostThreadViewPostModel | AppBskyFeedDefs.NotFoundPost
|
||||
replies?: (PostThreadViewPostModel | AppBskyFeedDefs.NotFoundPost)[]
|
||||
richText?: RichText
|
||||
|
||||
get uri() {
|
||||
|
@ -43,7 +44,7 @@ export class PostThreadViewPostModel {
|
|||
constructor(
|
||||
public rootStore: RootStoreModel,
|
||||
reactKey: string,
|
||||
v: GetPostThread.ThreadViewPost,
|
||||
v: AppBskyFeedDefs.ThreadViewPost,
|
||||
) {
|
||||
this._reactKey = reactKey
|
||||
this.post = v.post
|
||||
|
@ -51,11 +52,7 @@ export class PostThreadViewPostModel {
|
|||
const valid = FeedPost.validateRecord(this.post.record)
|
||||
if (valid.success) {
|
||||
this.postRecord = this.post.record
|
||||
this.richText = new RichText(
|
||||
this.postRecord.text,
|
||||
this.postRecord.entities,
|
||||
{cleanNewlines: true},
|
||||
)
|
||||
this.richText = new RichText(this.postRecord, {cleanNewlines: true})
|
||||
} else {
|
||||
rootStore.log.warn(
|
||||
'Received an invalid app.bsky.feed.post record',
|
||||
|
@ -74,14 +71,14 @@ export class PostThreadViewPostModel {
|
|||
|
||||
assignTreeModels(
|
||||
keyGen: Generator<string>,
|
||||
v: GetPostThread.ThreadViewPost,
|
||||
v: AppBskyFeedDefs.ThreadViewPost,
|
||||
higlightedPostUri: string,
|
||||
includeParent = true,
|
||||
includeChildren = true,
|
||||
) {
|
||||
// parents
|
||||
if (includeParent && v.parent) {
|
||||
if (GetPostThread.isThreadViewPost(v.parent)) {
|
||||
if (AppBskyFeedDefs.isThreadViewPost(v.parent)) {
|
||||
const parentModel = new PostThreadViewPostModel(
|
||||
this.rootStore,
|
||||
keyGen.next().value,
|
||||
|
@ -100,7 +97,7 @@ export class PostThreadViewPostModel {
|
|||
)
|
||||
}
|
||||
this.parent = parentModel
|
||||
} else if (GetPostThread.isNotFoundPost(v.parent)) {
|
||||
} else if (AppBskyFeedDefs.isNotFoundPost(v.parent)) {
|
||||
this.parent = v.parent
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +105,7 @@ export class PostThreadViewPostModel {
|
|||
if (includeChildren && v.replies) {
|
||||
const replies = []
|
||||
for (const item of v.replies) {
|
||||
if (GetPostThread.isThreadViewPost(item)) {
|
||||
if (AppBskyFeedDefs.isThreadViewPost(item)) {
|
||||
const itemModel = new PostThreadViewPostModel(
|
||||
this.rootStore,
|
||||
keyGen.next().value,
|
||||
|
@ -128,7 +125,7 @@ export class PostThreadViewPostModel {
|
|||
)
|
||||
}
|
||||
replies.push(itemModel)
|
||||
} else if (GetPostThread.isNotFoundPost(item)) {
|
||||
} else if (AppBskyFeedDefs.isNotFoundPost(item)) {
|
||||
replies.push(item)
|
||||
}
|
||||
}
|
||||
|
@ -136,68 +133,43 @@ export class PostThreadViewPostModel {
|
|||
}
|
||||
}
|
||||
|
||||
async toggleUpvote() {
|
||||
const wasUpvoted = !!this.post.viewer.upvote
|
||||
const wasDownvoted = !!this.post.viewer.downvote
|
||||
const res = await this.rootStore.api.app.bsky.feed.setVote({
|
||||
subject: {
|
||||
uri: this.post.uri,
|
||||
cid: this.post.cid,
|
||||
},
|
||||
direction: wasUpvoted ? 'none' : 'up',
|
||||
})
|
||||
runInAction(() => {
|
||||
if (wasDownvoted) {
|
||||
this.post.downvoteCount--
|
||||
}
|
||||
if (wasUpvoted) {
|
||||
this.post.upvoteCount--
|
||||
} else {
|
||||
this.post.upvoteCount++
|
||||
}
|
||||
this.post.viewer.upvote = res.data.upvote
|
||||
this.post.viewer.downvote = res.data.downvote
|
||||
})
|
||||
}
|
||||
|
||||
async toggleDownvote() {
|
||||
const wasUpvoted = !!this.post.viewer.upvote
|
||||
const wasDownvoted = !!this.post.viewer.downvote
|
||||
const res = await this.rootStore.api.app.bsky.feed.setVote({
|
||||
subject: {
|
||||
uri: this.post.uri,
|
||||
cid: this.post.cid,
|
||||
},
|
||||
direction: wasDownvoted ? 'none' : 'down',
|
||||
})
|
||||
runInAction(() => {
|
||||
if (wasUpvoted) {
|
||||
this.post.upvoteCount--
|
||||
}
|
||||
if (wasDownvoted) {
|
||||
this.post.downvoteCount--
|
||||
} else {
|
||||
this.post.downvoteCount++
|
||||
}
|
||||
this.post.viewer.upvote = res.data.upvote
|
||||
this.post.viewer.downvote = res.data.downvote
|
||||
})
|
||||
async toggleLike() {
|
||||
if (this.post.viewer?.like) {
|
||||
await this.rootStore.agent.deleteLike(this.post.viewer.like)
|
||||
runInAction(() => {
|
||||
this.post.likeCount = this.post.likeCount || 0
|
||||
this.post.viewer = this.post.viewer || {}
|
||||
this.post.likeCount--
|
||||
this.post.viewer.like = undefined
|
||||
})
|
||||
} else {
|
||||
const res = await this.rootStore.agent.like(this.post.uri, this.post.cid)
|
||||
runInAction(() => {
|
||||
this.post.likeCount = this.post.likeCount || 0
|
||||
this.post.viewer = this.post.viewer || {}
|
||||
this.post.likeCount++
|
||||
this.post.viewer.like = res.uri
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async toggleRepost() {
|
||||
if (this.post.viewer.repost) {
|
||||
await apilib.unrepost(this.rootStore, this.post.viewer.repost)
|
||||
if (this.post.viewer?.repost) {
|
||||
await this.rootStore.agent.deleteRepost(this.post.viewer.repost)
|
||||
runInAction(() => {
|
||||
this.post.repostCount = this.post.repostCount || 0
|
||||
this.post.viewer = this.post.viewer || {}
|
||||
this.post.repostCount--
|
||||
this.post.viewer.repost = undefined
|
||||
})
|
||||
} else {
|
||||
const res = await apilib.repost(
|
||||
this.rootStore,
|
||||
const res = await this.rootStore.agent.repost(
|
||||
this.post.uri,
|
||||
this.post.cid,
|
||||
)
|
||||
runInAction(() => {
|
||||
this.post.repostCount = this.post.repostCount || 0
|
||||
this.post.viewer = this.post.viewer || {}
|
||||
this.post.repostCount++
|
||||
this.post.viewer.repost = res.uri
|
||||
})
|
||||
|
@ -205,10 +177,7 @@ export class PostThreadViewPostModel {
|
|||
}
|
||||
|
||||
async delete() {
|
||||
await this.rootStore.api.app.bsky.feed.post.delete({
|
||||
did: this.post.author.did,
|
||||
rkey: new AtUri(this.post.uri).rkey,
|
||||
})
|
||||
await this.rootStore.agent.deletePost(this.post.uri)
|
||||
this.rootStore.emitPostDeleted(this.post.uri)
|
||||
}
|
||||
}
|
||||
|
@ -301,14 +270,14 @@ export class PostThreadViewModel {
|
|||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading(isRefreshing = false) {
|
||||
_xLoading(isRefreshing = false) {
|
||||
this.isLoading = true
|
||||
this.isRefreshing = isRefreshing
|
||||
this.error = ''
|
||||
this.notFound = false
|
||||
}
|
||||
|
||||
private _xIdle(err?: any) {
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.isRefreshing = false
|
||||
this.hasLoaded = true
|
||||
|
@ -322,7 +291,7 @@ export class PostThreadViewModel {
|
|||
// loader functions
|
||||
// =
|
||||
|
||||
private async _resolveUri() {
|
||||
async _resolveUri() {
|
||||
const urip = new AtUri(this.params.uri)
|
||||
if (!urip.host.startsWith('did:')) {
|
||||
try {
|
||||
|
@ -336,10 +305,10 @@ export class PostThreadViewModel {
|
|||
})
|
||||
}
|
||||
|
||||
private async _load(isRefreshing = false) {
|
||||
async _load(isRefreshing = false) {
|
||||
this._xLoading(isRefreshing)
|
||||
try {
|
||||
const res = await this.rootStore.api.app.bsky.feed.getPostThread(
|
||||
const res = await this.rootStore.agent.getPostThread(
|
||||
Object.assign({}, this.params, {uri: this.resolvedUri}),
|
||||
)
|
||||
this._replaceAll(res)
|
||||
|
@ -349,18 +318,18 @@ export class PostThreadViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
private _replaceAll(res: GetPostThread.Response) {
|
||||
_replaceAll(res: GetPostThread.Response) {
|
||||
sortThread(res.data.thread)
|
||||
const keyGen = reactKeyGenerator()
|
||||
const thread = new PostThreadViewPostModel(
|
||||
this.rootStore,
|
||||
keyGen.next().value,
|
||||
res.data.thread as GetPostThread.ThreadViewPost,
|
||||
res.data.thread as AppBskyFeedDefs.ThreadViewPost,
|
||||
)
|
||||
thread._isHighlightedPost = true
|
||||
thread.assignTreeModels(
|
||||
keyGen,
|
||||
res.data.thread as GetPostThread.ThreadViewPost,
|
||||
res.data.thread as AppBskyFeedDefs.ThreadViewPost,
|
||||
thread.uri,
|
||||
)
|
||||
this.thread = thread
|
||||
|
@ -368,25 +337,25 @@ export class PostThreadViewModel {
|
|||
}
|
||||
|
||||
type MaybePost =
|
||||
| GetPostThread.ThreadViewPost
|
||||
| GetPostThread.NotFoundPost
|
||||
| AppBskyFeedDefs.ThreadViewPost
|
||||
| AppBskyFeedDefs.NotFoundPost
|
||||
| {[k: string]: unknown; $type: string}
|
||||
function sortThread(post: MaybePost) {
|
||||
if (post.notFound) {
|
||||
return
|
||||
}
|
||||
post = post as GetPostThread.ThreadViewPost
|
||||
post = post as AppBskyFeedDefs.ThreadViewPost
|
||||
if (post.replies) {
|
||||
post.replies.sort((a: MaybePost, b: MaybePost) => {
|
||||
post = post as GetPostThread.ThreadViewPost
|
||||
post = post as AppBskyFeedDefs.ThreadViewPost
|
||||
if (a.notFound) {
|
||||
return 1
|
||||
}
|
||||
if (b.notFound) {
|
||||
return -1
|
||||
}
|
||||
a = a as GetPostThread.ThreadViewPost
|
||||
b = b as GetPostThread.ThreadViewPost
|
||||
a = a as AppBskyFeedDefs.ThreadViewPost
|
||||
b = b as AppBskyFeedDefs.ThreadViewPost
|
||||
const aIsByOp = a.post.author.did === post.post.author.did
|
||||
const bIsByOp = b.post.author.did === post.post.author.did
|
||||
if (aIsByOp && bIsByOp) {
|
||||
|
|
|
@ -58,12 +58,12 @@ export class PostModel implements RemoveIndex<Post.Record> {
|
|||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading() {
|
||||
_xLoading() {
|
||||
this.isLoading = true
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
private _xIdle(err?: any) {
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.hasLoaded = true
|
||||
this.error = cleanError(err)
|
||||
|
@ -75,12 +75,12 @@ export class PostModel implements RemoveIndex<Post.Record> {
|
|||
// loader functions
|
||||
// =
|
||||
|
||||
private async _load() {
|
||||
async _load() {
|
||||
this._xLoading()
|
||||
try {
|
||||
const urip = new AtUri(this.uri)
|
||||
const res = await this.rootStore.api.app.bsky.feed.post.get({
|
||||
user: urip.host,
|
||||
const res = await this.rootStore.agent.getPost({
|
||||
repo: urip.host,
|
||||
rkey: urip.rkey,
|
||||
})
|
||||
// TODO
|
||||
|
@ -94,7 +94,7 @@ export class PostModel implements RemoveIndex<Post.Record> {
|
|||
}
|
||||
}
|
||||
|
||||
private _replaceAll(res: Post.Record) {
|
||||
_replaceAll(res: Post.Record) {
|
||||
this.text = res.text
|
||||
this.entities = res.entities
|
||||
this.reply = res.reply
|
||||
|
|
|
@ -2,15 +2,12 @@ import {makeAutoObservable, runInAction} from 'mobx'
|
|||
import {PickedMedia} from 'lib/media/picker'
|
||||
import {
|
||||
AppBskyActorGetProfile as GetProfile,
|
||||
AppBskySystemDeclRef,
|
||||
AppBskyActorUpdateProfile,
|
||||
AppBskyActorProfile,
|
||||
RichText,
|
||||
} from '@atproto/api'
|
||||
type DeclRef = AppBskySystemDeclRef.Main
|
||||
import {extractEntities} from 'lib/strings/rich-text-detection'
|
||||
import {RootStoreModel} from './root-store'
|
||||
import * as apilib from 'lib/api/index'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {RichText} from 'lib/strings/rich-text'
|
||||
|
||||
export const ACTOR_TYPE_USER = 'app.bsky.system.actorUser'
|
||||
|
||||
|
@ -35,22 +32,18 @@ export class ProfileViewModel {
|
|||
// data
|
||||
did: string = ''
|
||||
handle: string = ''
|
||||
declaration: DeclRef = {
|
||||
cid: '',
|
||||
actorType: '',
|
||||
}
|
||||
creator: string = ''
|
||||
displayName?: string
|
||||
description?: string
|
||||
avatar?: string
|
||||
banner?: string
|
||||
displayName?: string = ''
|
||||
description?: string = ''
|
||||
avatar?: string = ''
|
||||
banner?: string = ''
|
||||
followersCount: number = 0
|
||||
followsCount: number = 0
|
||||
postsCount: number = 0
|
||||
viewer = new ProfileViewViewerModel()
|
||||
|
||||
// added data
|
||||
descriptionRichText?: RichText
|
||||
descriptionRichText?: RichText = new RichText({text: ''})
|
||||
|
||||
constructor(
|
||||
public rootStore: RootStoreModel,
|
||||
|
@ -79,10 +72,6 @@ export class ProfileViewModel {
|
|||
return this.hasLoaded && !this.hasContent
|
||||
}
|
||||
|
||||
get isUser() {
|
||||
return this.declaration.actorType === ACTOR_TYPE_USER
|
||||
}
|
||||
|
||||
// public api
|
||||
// =
|
||||
|
||||
|
@ -111,18 +100,14 @@ export class ProfileViewModel {
|
|||
}
|
||||
|
||||
if (followUri) {
|
||||
await apilib.unfollow(this.rootStore, followUri)
|
||||
await this.rootStore.agent.deleteFollow(followUri)
|
||||
runInAction(() => {
|
||||
this.followersCount--
|
||||
this.viewer.following = undefined
|
||||
this.rootStore.me.follows.removeFollow(this.did)
|
||||
})
|
||||
} else {
|
||||
const res = await apilib.follow(
|
||||
this.rootStore,
|
||||
this.did,
|
||||
this.declaration.cid,
|
||||
)
|
||||
const res = await this.rootStore.agent.follow(this.did)
|
||||
runInAction(() => {
|
||||
this.followersCount++
|
||||
this.viewer.following = res.uri
|
||||
|
@ -132,49 +117,48 @@ export class ProfileViewModel {
|
|||
}
|
||||
|
||||
async updateProfile(
|
||||
updates: AppBskyActorUpdateProfile.InputSchema,
|
||||
updates: AppBskyActorProfile.Record,
|
||||
newUserAvatar: PickedMedia | undefined | null,
|
||||
newUserBanner: PickedMedia | undefined | null,
|
||||
) {
|
||||
if (newUserAvatar) {
|
||||
const res = await apilib.uploadBlob(
|
||||
this.rootStore,
|
||||
newUserAvatar.path,
|
||||
newUserAvatar.mime,
|
||||
)
|
||||
updates.avatar = {
|
||||
cid: res.data.cid,
|
||||
mimeType: newUserAvatar.mime,
|
||||
await this.rootStore.agent.upsertProfile(async existing => {
|
||||
existing = existing || {}
|
||||
existing.displayName = updates.displayName
|
||||
existing.description = updates.description
|
||||
if (newUserAvatar) {
|
||||
const res = await apilib.uploadBlob(
|
||||
this.rootStore,
|
||||
newUserAvatar.path,
|
||||
newUserAvatar.mime,
|
||||
)
|
||||
existing.avatar = res.data.blob
|
||||
} else if (newUserAvatar === null) {
|
||||
existing.avatar = undefined
|
||||
}
|
||||
} else if (newUserAvatar === null) {
|
||||
updates.avatar = null
|
||||
}
|
||||
if (newUserBanner) {
|
||||
const res = await apilib.uploadBlob(
|
||||
this.rootStore,
|
||||
newUserBanner.path,
|
||||
newUserBanner.mime,
|
||||
)
|
||||
updates.banner = {
|
||||
cid: res.data.cid,
|
||||
mimeType: newUserBanner.mime,
|
||||
if (newUserBanner) {
|
||||
const res = await apilib.uploadBlob(
|
||||
this.rootStore,
|
||||
newUserBanner.path,
|
||||
newUserBanner.mime,
|
||||
)
|
||||
existing.banner = res.data.blob
|
||||
} else if (newUserBanner === null) {
|
||||
existing.banner = undefined
|
||||
}
|
||||
} else if (newUserBanner === null) {
|
||||
updates.banner = null
|
||||
}
|
||||
await this.rootStore.api.app.bsky.actor.updateProfile(updates)
|
||||
return existing
|
||||
})
|
||||
await this.rootStore.me.load()
|
||||
await this.refresh()
|
||||
}
|
||||
|
||||
async muteAccount() {
|
||||
await this.rootStore.api.app.bsky.graph.mute({user: this.did})
|
||||
await this.rootStore.agent.mute(this.did)
|
||||
this.viewer.muted = true
|
||||
await this.refresh()
|
||||
}
|
||||
|
||||
async unmuteAccount() {
|
||||
await this.rootStore.api.app.bsky.graph.unmute({user: this.did})
|
||||
await this.rootStore.agent.unmute(this.did)
|
||||
this.viewer.muted = false
|
||||
await this.refresh()
|
||||
}
|
||||
|
@ -182,13 +166,13 @@ export class ProfileViewModel {
|
|||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading(isRefreshing = false) {
|
||||
_xLoading(isRefreshing = false) {
|
||||
this.isLoading = true
|
||||
this.isRefreshing = isRefreshing
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
private _xIdle(err?: any) {
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.isRefreshing = false
|
||||
this.hasLoaded = true
|
||||
|
@ -201,40 +185,40 @@ export class ProfileViewModel {
|
|||
// loader functions
|
||||
// =
|
||||
|
||||
private async _load(isRefreshing = false) {
|
||||
async _load(isRefreshing = false) {
|
||||
this._xLoading(isRefreshing)
|
||||
try {
|
||||
const res = await this.rootStore.api.app.bsky.actor.getProfile(
|
||||
this.params,
|
||||
)
|
||||
const res = await this.rootStore.agent.getProfile(this.params)
|
||||
this.rootStore.profiles.overwrite(this.params.actor, res) // cache invalidation
|
||||
this._replaceAll(res)
|
||||
await this._createRichText()
|
||||
this._xIdle()
|
||||
} catch (e: any) {
|
||||
this._xIdle(e)
|
||||
}
|
||||
}
|
||||
|
||||
private _replaceAll(res: GetProfile.Response) {
|
||||
_replaceAll(res: GetProfile.Response) {
|
||||
this.did = res.data.did
|
||||
this.handle = res.data.handle
|
||||
Object.assign(this.declaration, res.data.declaration)
|
||||
this.creator = res.data.creator
|
||||
this.displayName = res.data.displayName
|
||||
this.description = res.data.description
|
||||
this.avatar = res.data.avatar
|
||||
this.banner = res.data.banner
|
||||
this.followersCount = res.data.followersCount
|
||||
this.followsCount = res.data.followsCount
|
||||
this.postsCount = res.data.postsCount
|
||||
this.followersCount = res.data.followersCount || 0
|
||||
this.followsCount = res.data.followsCount || 0
|
||||
this.postsCount = res.data.postsCount || 0
|
||||
if (res.data.viewer) {
|
||||
Object.assign(this.viewer, res.data.viewer)
|
||||
this.rootStore.me.follows.hydrate(this.did, res.data.viewer.following)
|
||||
}
|
||||
}
|
||||
|
||||
async _createRichText() {
|
||||
this.descriptionRichText = new RichText(
|
||||
this.description || '',
|
||||
extractEntities(this.description || ''),
|
||||
{text: this.description || ''},
|
||||
{cleanNewlines: true},
|
||||
)
|
||||
await this.descriptionRichText.detectFacets(this.rootStore.agent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ export class ProfilesViewModel {
|
|||
}
|
||||
}
|
||||
try {
|
||||
const promise = this.rootStore.api.app.bsky.actor.getProfile({
|
||||
const promise = this.rootStore.agent.getProfile({
|
||||
actor: did,
|
||||
})
|
||||
this.cache.set(did, promise)
|
||||
|
|
|
@ -2,7 +2,7 @@ import {makeAutoObservable, runInAction} from 'mobx'
|
|||
import {AtUri} from '../../third-party/uri'
|
||||
import {
|
||||
AppBskyFeedGetRepostedBy as GetRepostedBy,
|
||||
AppBskyActorRef as ActorRef,
|
||||
AppBskyActorDefs,
|
||||
} from '@atproto/api'
|
||||
import {RootStoreModel} from './root-store'
|
||||
import {bundleAsync} from 'lib/async/bundle'
|
||||
|
@ -11,7 +11,7 @@ import * as apilib from 'lib/api/index'
|
|||
|
||||
const PAGE_SIZE = 30
|
||||
|
||||
export type RepostedByItem = ActorRef.WithInfo
|
||||
export type RepostedByItem = AppBskyActorDefs.ProfileViewBasic
|
||||
|
||||
export class RepostedByViewModel {
|
||||
// state
|
||||
|
@ -71,9 +71,9 @@ export class RepostedByViewModel {
|
|||
const params = Object.assign({}, this.params, {
|
||||
uri: this.resolvedUri,
|
||||
limit: PAGE_SIZE,
|
||||
before: replace ? undefined : this.loadMoreCursor,
|
||||
cursor: replace ? undefined : this.loadMoreCursor,
|
||||
})
|
||||
const res = await this.rootStore.api.app.bsky.feed.getRepostedBy(params)
|
||||
const res = await this.rootStore.agent.getRepostedBy(params)
|
||||
if (replace) {
|
||||
this._replaceAll(res)
|
||||
} else {
|
||||
|
@ -88,13 +88,13 @@ export class RepostedByViewModel {
|
|||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading(isRefreshing = false) {
|
||||
_xLoading(isRefreshing = false) {
|
||||
this.isLoading = true
|
||||
this.isRefreshing = isRefreshing
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
private _xIdle(err?: any) {
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.isRefreshing = false
|
||||
this.hasLoaded = true
|
||||
|
@ -107,7 +107,7 @@ export class RepostedByViewModel {
|
|||
// helper functions
|
||||
// =
|
||||
|
||||
private async _resolveUri() {
|
||||
async _resolveUri() {
|
||||
const urip = new AtUri(this.params.uri)
|
||||
if (!urip.host.startsWith('did:')) {
|
||||
try {
|
||||
|
@ -121,12 +121,12 @@ export class RepostedByViewModel {
|
|||
})
|
||||
}
|
||||
|
||||
private _replaceAll(res: GetRepostedBy.Response) {
|
||||
_replaceAll(res: GetRepostedBy.Response) {
|
||||
this.repostedBy = []
|
||||
this._appendAll(res)
|
||||
}
|
||||
|
||||
private _appendAll(res: GetRepostedBy.Response) {
|
||||
_appendAll(res: GetRepostedBy.Response) {
|
||||
this.loadMoreCursor = res.data.cursor
|
||||
this.hasMore = !!this.loadMoreCursor
|
||||
this.repostedBy = this.repostedBy.concat(res.data.repostedBy)
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
* The root store is the base of all modeled state.
|
||||
*/
|
||||
|
||||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import {AtpAgent} from '@atproto/api'
|
||||
import {makeAutoObservable} from 'mobx'
|
||||
import {BskyAgent} from '@atproto/api'
|
||||
import {createContext, useContext} from 'react'
|
||||
import {DeviceEventEmitter, EmitterSubscription} from 'react-native'
|
||||
import * as BgScheduler from 'lib/bg-scheduler'
|
||||
|
@ -29,7 +29,7 @@ export const appInfo = z.object({
|
|||
export type AppInfo = z.infer<typeof appInfo>
|
||||
|
||||
export class RootStoreModel {
|
||||
agent: AtpAgent
|
||||
agent: BskyAgent
|
||||
appInfo?: AppInfo
|
||||
log = new LogModel()
|
||||
session = new SessionModel(this)
|
||||
|
@ -40,41 +40,16 @@ export class RootStoreModel {
|
|||
linkMetas = new LinkMetasCache(this)
|
||||
imageSizes = new ImageSizesCache()
|
||||
|
||||
// HACK
|
||||
// this flag is to track the lexicon breaking refactor
|
||||
// it should be removed once we get that done
|
||||
// -prf
|
||||
hackUpgradeNeeded = false
|
||||
async hackCheckIfUpgradeNeeded() {
|
||||
try {
|
||||
this.log.debug('hackCheckIfUpgradeNeeded()')
|
||||
const res = await fetch('https://bsky.social/xrpc/app.bsky.feed.getLikes')
|
||||
await res.text()
|
||||
runInAction(() => {
|
||||
this.hackUpgradeNeeded = res.status !== 501
|
||||
this.log.debug(
|
||||
`hackCheckIfUpgradeNeeded() said ${this.hackUpgradeNeeded}`,
|
||||
)
|
||||
})
|
||||
} catch (e) {
|
||||
this.log.error('Failed to hackCheckIfUpgradeNeeded', {e})
|
||||
}
|
||||
}
|
||||
|
||||
constructor(agent: AtpAgent) {
|
||||
constructor(agent: BskyAgent) {
|
||||
this.agent = agent
|
||||
makeAutoObservable(this, {
|
||||
api: false,
|
||||
agent: false,
|
||||
serialize: false,
|
||||
hydrate: false,
|
||||
})
|
||||
this.initBgFetch()
|
||||
}
|
||||
|
||||
get api() {
|
||||
return this.agent.api
|
||||
}
|
||||
|
||||
setAppInfo(info: AppInfo) {
|
||||
this.appInfo = info
|
||||
}
|
||||
|
@ -131,7 +106,7 @@ export class RootStoreModel {
|
|||
/**
|
||||
* Called by the session model. Refreshes session-oriented state.
|
||||
*/
|
||||
async handleSessionChange(agent: AtpAgent) {
|
||||
async handleSessionChange(agent: BskyAgent) {
|
||||
this.log.debug('RootStoreModel:handleSessionChange')
|
||||
this.agent = agent
|
||||
this.me.clear()
|
||||
|
@ -259,7 +234,7 @@ export class RootStoreModel {
|
|||
async onBgFetch(taskId: string) {
|
||||
this.log.debug(`Background fetch fired for task ${taskId}`)
|
||||
if (this.session.hasSession) {
|
||||
const res = await this.api.app.bsky.notification.getCount()
|
||||
const res = await this.agent.countUnreadNotifications()
|
||||
const hasNewNotifs = this.me.notifications.unreadCount !== res.data.count
|
||||
this.emitUnreadNotifications(res.data.count)
|
||||
this.log.debug(
|
||||
|
@ -286,7 +261,7 @@ export class RootStoreModel {
|
|||
}
|
||||
|
||||
const throwawayInst = new RootStoreModel(
|
||||
new AtpAgent({service: 'http://localhost'}),
|
||||
new BskyAgent({service: 'http://localhost'}),
|
||||
) // this will be replaced by the loader, we just need to supply a value at init
|
||||
const RootStoreContext = createContext<RootStoreModel>(throwawayInst)
|
||||
export const RootStoreProvider = RootStoreContext.Provider
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import {
|
||||
AtpAgent,
|
||||
BskyAgent,
|
||||
AtpSessionEvent,
|
||||
AtpSessionData,
|
||||
ComAtprotoServerGetAccountsConfig as GetAccountsConfig,
|
||||
ComAtprotoServerDescribeServer as DescribeServer,
|
||||
} from '@atproto/api'
|
||||
import normalizeUrl from 'normalize-url'
|
||||
import {isObj, hasProp} from 'lib/type-guards'
|
||||
|
@ -11,7 +11,7 @@ import {networkRetry} from 'lib/async/retry'
|
|||
import {z} from 'zod'
|
||||
import {RootStoreModel} from './root-store'
|
||||
|
||||
export type ServiceDescription = GetAccountsConfig.OutputSchema
|
||||
export type ServiceDescription = DescribeServer.OutputSchema
|
||||
|
||||
export const activeSession = z.object({
|
||||
service: z.string(),
|
||||
|
@ -40,7 +40,7 @@ export class SessionModel {
|
|||
// emergency log facility to help us track down this logout issue
|
||||
// remove when resolved
|
||||
// -prf
|
||||
private _log(message: string, details?: Record<string, any>) {
|
||||
_log(message: string, details?: Record<string, any>) {
|
||||
details = details || {}
|
||||
details.state = {
|
||||
data: this.data,
|
||||
|
@ -73,6 +73,7 @@ export class SessionModel {
|
|||
rootStore: false,
|
||||
serialize: false,
|
||||
hydrate: false,
|
||||
hasSession: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -154,7 +155,7 @@ export class SessionModel {
|
|||
/**
|
||||
* Sets the active session
|
||||
*/
|
||||
async setActiveSession(agent: AtpAgent, did: string) {
|
||||
async setActiveSession(agent: BskyAgent, did: string) {
|
||||
this._log('SessionModel:setActiveSession')
|
||||
this.data = {
|
||||
service: agent.service.toString(),
|
||||
|
@ -166,7 +167,7 @@ export class SessionModel {
|
|||
/**
|
||||
* Upserts a session into the accounts
|
||||
*/
|
||||
private persistSession(
|
||||
persistSession(
|
||||
service: string,
|
||||
did: string,
|
||||
event: AtpSessionEvent,
|
||||
|
@ -225,7 +226,7 @@ export class SessionModel {
|
|||
/**
|
||||
* Clears any session tokens from the accounts; used on logout.
|
||||
*/
|
||||
private clearSessionTokens() {
|
||||
clearSessionTokens() {
|
||||
this._log('SessionModel:clearSessionTokens')
|
||||
this.accounts = this.accounts.map(acct => ({
|
||||
service: acct.service,
|
||||
|
@ -239,10 +240,8 @@ export class SessionModel {
|
|||
/**
|
||||
* Fetches additional information about an account on load.
|
||||
*/
|
||||
private async loadAccountInfo(agent: AtpAgent, did: string) {
|
||||
const res = await agent.api.app.bsky.actor
|
||||
.getProfile({actor: did})
|
||||
.catch(_e => undefined)
|
||||
async loadAccountInfo(agent: BskyAgent, did: string) {
|
||||
const res = await agent.getProfile({actor: did}).catch(_e => undefined)
|
||||
if (res) {
|
||||
return {
|
||||
dispayName: res.data.displayName,
|
||||
|
@ -255,8 +254,8 @@ export class SessionModel {
|
|||
* Helper to fetch the accounts config settings from an account.
|
||||
*/
|
||||
async describeService(service: string): Promise<ServiceDescription> {
|
||||
const agent = new AtpAgent({service})
|
||||
const res = await agent.api.com.atproto.server.getAccountsConfig({})
|
||||
const agent = new BskyAgent({service})
|
||||
const res = await agent.com.atproto.server.describeServer({})
|
||||
return res.data
|
||||
}
|
||||
|
||||
|
@ -272,7 +271,7 @@ export class SessionModel {
|
|||
return false
|
||||
}
|
||||
|
||||
const agent = new AtpAgent({
|
||||
const agent = new BskyAgent({
|
||||
service: account.service,
|
||||
persistSession: (evt: AtpSessionEvent, sess?: AtpSessionData) => {
|
||||
this.persistSession(account.service, account.did, evt, sess)
|
||||
|
@ -321,7 +320,7 @@ export class SessionModel {
|
|||
password: string
|
||||
}) {
|
||||
this._log('SessionModel:login')
|
||||
const agent = new AtpAgent({service})
|
||||
const agent = new BskyAgent({service})
|
||||
await agent.login({identifier, password})
|
||||
if (!agent.session) {
|
||||
throw new Error('Failed to establish session')
|
||||
|
@ -355,7 +354,7 @@ export class SessionModel {
|
|||
inviteCode?: string
|
||||
}) {
|
||||
this._log('SessionModel:createAccount')
|
||||
const agent = new AtpAgent({service})
|
||||
const agent = new BskyAgent({service})
|
||||
await agent.createAccount({
|
||||
handle,
|
||||
password,
|
||||
|
@ -389,7 +388,7 @@ export class SessionModel {
|
|||
// need to evaluate why deleting the session has caused errors at times
|
||||
// -prf
|
||||
/*if (this.hasSession) {
|
||||
this.rootStore.api.com.atproto.session.delete().catch((e: any) => {
|
||||
this.rootStore.agent.com.atproto.session.delete().catch((e: any) => {
|
||||
this.rootStore.log.warn(
|
||||
'(Minor issue) Failed to delete session on the server',
|
||||
e,
|
||||
|
@ -415,7 +414,7 @@ export class SessionModel {
|
|||
if (!sess) {
|
||||
return
|
||||
}
|
||||
const res = await this.rootStore.api.app.bsky.actor
|
||||
const res = await this.rootStore.agent
|
||||
.getProfile({actor: sess.did})
|
||||
.catch(_e => undefined)
|
||||
if (res?.success) {
|
||||
|
|
|
@ -72,12 +72,12 @@ export class SuggestedPostsView {
|
|||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading() {
|
||||
_xLoading() {
|
||||
this.isLoading = true
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
private _xIdle(err?: any) {
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.hasLoaded = true
|
||||
this.error = cleanError(err)
|
||||
|
|
|
@ -2,7 +2,7 @@ import {makeAutoObservable} from 'mobx'
|
|||
import {RootStoreModel} from '../root-store'
|
||||
import {ServiceDescription} from '../session'
|
||||
import {DEFAULT_SERVICE} from 'state/index'
|
||||
import {ComAtprotoAccountCreate} from '@atproto/api'
|
||||
import {ComAtprotoServerCreateAccount} from '@atproto/api'
|
||||
import * as EmailValidator from 'email-validator'
|
||||
import {createFullHandle} from 'lib/strings/handles'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
|
@ -99,7 +99,7 @@ export class CreateAccountModel {
|
|||
})
|
||||
} catch (e: any) {
|
||||
let errMsg = e.toString()
|
||||
if (e instanceof ComAtprotoAccountCreate.InvalidInviteCodeError) {
|
||||
if (e instanceof ComAtprotoServerCreateAccount.InvalidInviteCodeError) {
|
||||
errMsg =
|
||||
'Invite code not accepted. Check that you input it correctly and try again.'
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ export class ProfileUiModel {
|
|||
)
|
||||
this.profile = new ProfileViewModel(rootStore, {actor: params.user})
|
||||
this.feed = new FeedModel(rootStore, 'author', {
|
||||
author: params.user,
|
||||
actor: params.user,
|
||||
limit: 10,
|
||||
})
|
||||
}
|
||||
|
@ -64,16 +64,8 @@ export class ProfileUiModel {
|
|||
return this.profile.isRefreshing || this.currentView.isRefreshing
|
||||
}
|
||||
|
||||
get isUser() {
|
||||
return this.profile.isUser
|
||||
}
|
||||
|
||||
get selectorItems() {
|
||||
if (this.isUser) {
|
||||
return USER_SELECTOR_ITEMS
|
||||
} else {
|
||||
return USER_SELECTOR_ITEMS
|
||||
}
|
||||
return USER_SELECTOR_ITEMS
|
||||
}
|
||||
|
||||
get selectedView() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import {searchProfiles, searchPosts} from 'lib/api/search'
|
||||
import {AppBskyActorProfile as Profile} from '@atproto/api'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
|
||||
export class SearchUIModel {
|
||||
|
@ -8,7 +8,7 @@ export class SearchUIModel {
|
|||
isProfilesLoading = false
|
||||
query: string = ''
|
||||
postUris: string[] = []
|
||||
profiles: Profile.View[] = []
|
||||
profiles: AppBskyActorDefs.ProfileView[] = []
|
||||
|
||||
constructor(public rootStore: RootStoreModel) {
|
||||
makeAutoObservable(this)
|
||||
|
@ -34,10 +34,10 @@ export class SearchUIModel {
|
|||
this.isPostsLoading = false
|
||||
})
|
||||
|
||||
let profiles: Profile.View[] = []
|
||||
let profiles: AppBskyActorDefs.ProfileView[] = []
|
||||
if (profilesSearch?.length) {
|
||||
do {
|
||||
const res = await this.rootStore.api.app.bsky.actor.getProfiles({
|
||||
const res = await this.rootStore.agent.getProfiles({
|
||||
actors: profilesSearch.splice(0, 25).map(p => p.did),
|
||||
})
|
||||
profiles = profiles.concat(res.data.profiles)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import {AppBskyEmbedRecord} from '@atproto/api'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
import {makeAutoObservable} from 'mobx'
|
||||
import {ProfileViewModel} from '../profile-view'
|
||||
|
@ -111,6 +112,7 @@ export interface ComposerOptsQuote {
|
|||
displayName?: string
|
||||
avatar?: string
|
||||
}
|
||||
embeds?: AppBskyEmbedRecord.ViewRecord['embeds']
|
||||
}
|
||||
export interface ComposerOpts {
|
||||
replyTo?: ComposerOptsPostRef
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import {AppBskyActorRef} from '@atproto/api'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import AwaitLock from 'await-lock'
|
||||
import {RootStoreModel} from './root-store'
|
||||
|
||||
|
@ -11,8 +11,8 @@ export class UserAutocompleteViewModel {
|
|||
lock = new AwaitLock()
|
||||
|
||||
// data
|
||||
follows: AppBskyActorRef.WithInfo[] = []
|
||||
searchRes: AppBskyActorRef.WithInfo[] = []
|
||||
follows: AppBskyActorDefs.ProfileViewBasic[] = []
|
||||
searchRes: AppBskyActorDefs.ProfileViewBasic[] = []
|
||||
knownHandles: Set<string> = new Set()
|
||||
|
||||
constructor(public rootStore: RootStoreModel) {
|
||||
|
@ -76,9 +76,9 @@ export class UserAutocompleteViewModel {
|
|||
// internal
|
||||
// =
|
||||
|
||||
private async _getFollows() {
|
||||
const res = await this.rootStore.api.app.bsky.graph.getFollows({
|
||||
user: this.rootStore.me.did || '',
|
||||
async _getFollows() {
|
||||
const res = await this.rootStore.agent.getFollows({
|
||||
actor: this.rootStore.me.did || '',
|
||||
})
|
||||
runInAction(() => {
|
||||
this.follows = res.data.follows
|
||||
|
@ -88,13 +88,13 @@ export class UserAutocompleteViewModel {
|
|||
})
|
||||
}
|
||||
|
||||
private async _search() {
|
||||
const res = await this.rootStore.api.app.bsky.actor.searchTypeahead({
|
||||
async _search() {
|
||||
const res = await this.rootStore.agent.searchActorsTypeahead({
|
||||
term: this.prefix,
|
||||
limit: 8,
|
||||
})
|
||||
runInAction(() => {
|
||||
this.searchRes = res.data.users
|
||||
this.searchRes = res.data.actors
|
||||
for (const u of this.searchRes) {
|
||||
this.knownHandles.add(u.handle)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {makeAutoObservable} from 'mobx'
|
||||
import {
|
||||
AppBskyGraphGetFollowers as GetFollowers,
|
||||
AppBskyActorRef as ActorRef,
|
||||
AppBskyActorDefs as ActorDefs,
|
||||
} from '@atproto/api'
|
||||
import {RootStoreModel} from './root-store'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
|
@ -9,7 +9,7 @@ import {bundleAsync} from 'lib/async/bundle'
|
|||
|
||||
const PAGE_SIZE = 30
|
||||
|
||||
export type FollowerItem = ActorRef.WithInfo
|
||||
export type FollowerItem = ActorDefs.ProfileViewBasic
|
||||
|
||||
export class UserFollowersViewModel {
|
||||
// state
|
||||
|
@ -22,10 +22,9 @@ export class UserFollowersViewModel {
|
|||
loadMoreCursor?: string
|
||||
|
||||
// data
|
||||
subject: ActorRef.WithInfo = {
|
||||
subject: ActorDefs.ProfileViewBasic = {
|
||||
did: '',
|
||||
handle: '',
|
||||
declaration: {cid: '', actorType: ''},
|
||||
}
|
||||
followers: FollowerItem[] = []
|
||||
|
||||
|
@ -71,9 +70,9 @@ export class UserFollowersViewModel {
|
|||
try {
|
||||
const params = Object.assign({}, this.params, {
|
||||
limit: PAGE_SIZE,
|
||||
before: replace ? undefined : this.loadMoreCursor,
|
||||
cursor: replace ? undefined : this.loadMoreCursor,
|
||||
})
|
||||
const res = await this.rootStore.api.app.bsky.graph.getFollowers(params)
|
||||
const res = await this.rootStore.agent.getFollowers(params)
|
||||
if (replace) {
|
||||
this._replaceAll(res)
|
||||
} else {
|
||||
|
@ -88,13 +87,13 @@ export class UserFollowersViewModel {
|
|||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading(isRefreshing = false) {
|
||||
_xLoading(isRefreshing = false) {
|
||||
this.isLoading = true
|
||||
this.isRefreshing = isRefreshing
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
private _xIdle(err?: any) {
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.isRefreshing = false
|
||||
this.hasLoaded = true
|
||||
|
@ -107,12 +106,12 @@ export class UserFollowersViewModel {
|
|||
// helper functions
|
||||
// =
|
||||
|
||||
private _replaceAll(res: GetFollowers.Response) {
|
||||
_replaceAll(res: GetFollowers.Response) {
|
||||
this.followers = []
|
||||
this._appendAll(res)
|
||||
}
|
||||
|
||||
private _appendAll(res: GetFollowers.Response) {
|
||||
_appendAll(res: GetFollowers.Response) {
|
||||
this.loadMoreCursor = res.data.cursor
|
||||
this.hasMore = !!this.loadMoreCursor
|
||||
this.followers = this.followers.concat(res.data.followers)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {makeAutoObservable} from 'mobx'
|
||||
import {
|
||||
AppBskyGraphGetFollows as GetFollows,
|
||||
AppBskyActorRef as ActorRef,
|
||||
AppBskyActorDefs as ActorDefs,
|
||||
} from '@atproto/api'
|
||||
import {RootStoreModel} from './root-store'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
|
@ -9,7 +9,7 @@ import {bundleAsync} from 'lib/async/bundle'
|
|||
|
||||
const PAGE_SIZE = 30
|
||||
|
||||
export type FollowItem = ActorRef.WithInfo
|
||||
export type FollowItem = ActorDefs.ProfileViewBasic
|
||||
|
||||
export class UserFollowsViewModel {
|
||||
// state
|
||||
|
@ -22,10 +22,9 @@ export class UserFollowsViewModel {
|
|||
loadMoreCursor?: string
|
||||
|
||||
// data
|
||||
subject: ActorRef.WithInfo = {
|
||||
subject: ActorDefs.ProfileViewBasic = {
|
||||
did: '',
|
||||
handle: '',
|
||||
declaration: {cid: '', actorType: ''},
|
||||
}
|
||||
follows: FollowItem[] = []
|
||||
|
||||
|
@ -71,9 +70,9 @@ export class UserFollowsViewModel {
|
|||
try {
|
||||
const params = Object.assign({}, this.params, {
|
||||
limit: PAGE_SIZE,
|
||||
before: replace ? undefined : this.loadMoreCursor,
|
||||
cursor: replace ? undefined : this.loadMoreCursor,
|
||||
})
|
||||
const res = await this.rootStore.api.app.bsky.graph.getFollows(params)
|
||||
const res = await this.rootStore.agent.getFollows(params)
|
||||
if (replace) {
|
||||
this._replaceAll(res)
|
||||
} else {
|
||||
|
@ -88,13 +87,13 @@ export class UserFollowsViewModel {
|
|||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading(isRefreshing = false) {
|
||||
_xLoading(isRefreshing = false) {
|
||||
this.isLoading = true
|
||||
this.isRefreshing = isRefreshing
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
private _xIdle(err?: any) {
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.isRefreshing = false
|
||||
this.hasLoaded = true
|
||||
|
@ -107,12 +106,12 @@ export class UserFollowsViewModel {
|
|||
// helper functions
|
||||
// =
|
||||
|
||||
private _replaceAll(res: GetFollows.Response) {
|
||||
_replaceAll(res: GetFollows.Response) {
|
||||
this.follows = []
|
||||
this._appendAll(res)
|
||||
}
|
||||
|
||||
private _appendAll(res: GetFollows.Response) {
|
||||
_appendAll(res: GetFollows.Response) {
|
||||
this.loadMoreCursor = res.data.cursor
|
||||
this.hasMore = !!this.loadMoreCursor
|
||||
this.follows = this.follows.concat(res.data.follows)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue