diff options
Diffstat (limited to 'src/state/models')
-rw-r--r-- | src/state/models/content/post-thread.ts | 43 | ||||
-rw-r--r-- | src/state/models/content/profile.ts | 32 | ||||
-rw-r--r-- | src/state/models/feeds/notifications.ts | 4 | ||||
-rw-r--r-- | src/state/models/feeds/posts.ts | 25 | ||||
-rw-r--r-- | src/state/models/lists/blocked-accounts.ts | 106 |
5 files changed, 207 insertions, 3 deletions
diff --git a/src/state/models/content/post-thread.ts b/src/state/models/content/post-thread.ts index 8f9a55032..18a42732c 100644 --- a/src/state/models/content/post-thread.ts +++ b/src/state/models/content/post-thread.ts @@ -13,6 +13,9 @@ import {updateDataOptimistically} from 'lib/async/revertible' import {PostLabelInfo, PostModeration} from 'lib/labeling/types' import { getEmbedLabels, + getEmbedMuted, + getEmbedBlocking, + getEmbedBlockedBy, filterAccountLabels, filterProfileLabels, getPostModeration, @@ -30,7 +33,10 @@ export class PostThreadItemModel { // data post: AppBskyFeedDefs.PostView postRecord?: FeedPost.Record - parent?: PostThreadItemModel | AppBskyFeedDefs.NotFoundPost + parent?: + | PostThreadItemModel + | AppBskyFeedDefs.NotFoundPost + | AppBskyFeedDefs.BlockedPost replies?: (PostThreadItemModel | AppBskyFeedDefs.NotFoundPost)[] richText?: RichText @@ -60,7 +66,18 @@ export class PostThreadItemModel { ), accountLabels: filterAccountLabels(this.post.author.labels), profileLabels: filterProfileLabels(this.post.author.labels), - isMuted: this.post.author.viewer?.muted || false, + isMuted: + this.post.author.viewer?.muted || + getEmbedMuted(this.post.embed) || + false, + isBlocking: + !!this.post.author.viewer?.blocking || + getEmbedBlocking(this.post.embed) || + false, + isBlockedBy: + !!this.post.author.viewer?.blockedBy || + getEmbedBlockedBy(this.post.embed) || + false, } } @@ -114,6 +131,8 @@ export class PostThreadItemModel { this.parent = parentModel } else if (AppBskyFeedDefs.isNotFoundPost(v.parent)) { this.parent = v.parent + } else if (AppBskyFeedDefs.isBlockedPost(v.parent)) { + this.parent = v.parent } } // replies @@ -218,6 +237,7 @@ export class PostThreadModel { // data thread?: PostThreadItemModel + isBlocked = false constructor( public rootStore: RootStoreModel, @@ -377,11 +397,17 @@ export class PostThreadModel { this._replaceAll(res) this._xIdle() } catch (e: any) { + console.log(e) this._xIdle(e) } } _replaceAll(res: GetPostThread.Response) { + this.isBlocked = AppBskyFeedDefs.isBlockedPost(res.data.thread) + if (this.isBlocked) { + return + } + pruneReplies(res.data.thread) sortThread(res.data.thread) const thread = new PostThreadItemModel( this.rootStore, @@ -399,7 +425,20 @@ export class PostThreadModel { type MaybePost = | AppBskyFeedDefs.ThreadViewPost | AppBskyFeedDefs.NotFoundPost + | AppBskyFeedDefs.BlockedPost | {[k: string]: unknown; $type: string} +function pruneReplies(post: MaybePost) { + if (post.replies) { + post.replies = (post.replies as MaybePost[]).filter((reply: MaybePost) => { + if (reply.blocked) { + return false + } + pruneReplies(reply) + return true + }) + } +} + function sortThread(post: MaybePost) { if (post.notFound) { return diff --git a/src/state/models/content/profile.ts b/src/state/models/content/profile.ts index ea75d19c6..dddf488a3 100644 --- a/src/state/models/content/profile.ts +++ b/src/state/models/content/profile.ts @@ -1,5 +1,6 @@ import {makeAutoObservable, runInAction} from 'mobx' import { + AtUri, ComAtprotoLabelDefs, AppBskyActorGetProfile as GetProfile, AppBskyActorProfile, @@ -23,6 +24,8 @@ export class ProfileViewerModel { muted?: boolean following?: string followedBy?: string + blockedBy?: boolean + blocking?: string constructor() { makeAutoObservable(this) @@ -86,6 +89,8 @@ export class ProfileModel { accountLabels: filterAccountLabels(this.labels), profileLabels: filterProfileLabels(this.labels), isMuted: this.viewer?.muted || false, + isBlocking: !!this.viewer?.blocking || false, + isBlockedBy: !!this.viewer?.blockedBy || false, } } @@ -185,6 +190,33 @@ export class ProfileModel { await this.refresh() } + async blockAccount() { + const res = await this.rootStore.agent.app.bsky.graph.block.create( + { + repo: this.rootStore.me.did, + }, + { + subject: this.did, + createdAt: new Date().toISOString(), + }, + ) + this.viewer.blocking = res.uri + await this.refresh() + } + + async unblockAccount() { + if (!this.viewer.blocking) { + return + } + const {rkey} = new AtUri(this.viewer.blocking) + await this.rootStore.agent.app.bsky.graph.block.delete({ + repo: this.rootStore.me.did, + rkey, + }) + this.viewer.blocking = undefined + await this.refresh() + } + // state transitions // = diff --git a/src/state/models/feeds/notifications.ts b/src/state/models/feeds/notifications.ts index 02f58819f..3ffd10b99 100644 --- a/src/state/models/feeds/notifications.ts +++ b/src/state/models/feeds/notifications.ts @@ -111,6 +111,10 @@ export class NotificationsFeedItemModel { addedInfo?.profileLabels || [], ), isMuted: this.author.viewer?.muted || addedInfo?.isMuted || false, + isBlocking: + !!this.author.viewer?.blocking || addedInfo?.isBlocking || false, + isBlockedBy: + !!this.author.viewer?.blockedBy || addedInfo?.isBlockedBy || false, } } diff --git a/src/state/models/feeds/posts.ts b/src/state/models/feeds/posts.ts index 62c6da3de..62047acba 100644 --- a/src/state/models/feeds/posts.ts +++ b/src/state/models/feeds/posts.ts @@ -23,7 +23,11 @@ import {updateDataOptimistically} from 'lib/async/revertible' import {PostLabelInfo, PostModeration} from 'lib/labeling/types' import { getEmbedLabels, + getEmbedMuted, + getEmbedBlocking, + getEmbedBlockedBy, getPostModeration, + mergePostModerations, filterAccountLabels, filterProfileLabels, } from 'lib/labeling/helpers' @@ -97,7 +101,18 @@ export class PostsFeedItemModel { ), accountLabels: filterAccountLabels(this.post.author.labels), profileLabels: filterProfileLabels(this.post.author.labels), - isMuted: this.post.author.viewer?.muted || false, + isMuted: + this.post.author.viewer?.muted || + getEmbedMuted(this.post.embed) || + false, + isBlocking: + !!this.post.author.viewer?.blocking || + getEmbedBlocking(this.post.embed) || + false, + isBlockedBy: + !!this.post.author.viewer?.blockedBy || + getEmbedBlockedBy(this.post.embed) || + false, } } @@ -240,6 +255,10 @@ export class PostsFeedSliceModel { return this.items[0] } + get moderation() { + return mergePostModerations(this.items.map(item => item.moderation)) + } + containsUri(uri: string) { return !!this.items.find(item => item.post.uri === uri) } @@ -265,6 +284,8 @@ export class PostsFeedModel { isRefreshing = false hasNewLatest = false hasLoaded = false + isBlocking = false + isBlockedBy = false error = '' loadMoreError = '' params: GetTimeline.QueryParams | GetAuthorFeed.QueryParams @@ -553,6 +574,8 @@ export class PostsFeedModel { this.isLoading = false this.isRefreshing = false this.hasLoaded = true + this.isBlocking = error instanceof GetAuthorFeed.BlockedActorError + this.isBlockedBy = error instanceof GetAuthorFeed.BlockedByActorError this.error = cleanError(error) this.loadMoreError = cleanError(loadMoreError) if (error) { diff --git a/src/state/models/lists/blocked-accounts.ts b/src/state/models/lists/blocked-accounts.ts new file mode 100644 index 000000000..20eef8aff --- /dev/null +++ b/src/state/models/lists/blocked-accounts.ts @@ -0,0 +1,106 @@ +import {makeAutoObservable} from 'mobx' +import { + AppBskyGraphGetBlocks as GetBlocks, + AppBskyActorDefs as ActorDefs, +} from '@atproto/api' +import {RootStoreModel} from '../root-store' +import {cleanError} from 'lib/strings/errors' +import {bundleAsync} from 'lib/async/bundle' + +const PAGE_SIZE = 30 + +export class BlockedAccountsModel { + // state + isLoading = false + isRefreshing = false + hasLoaded = false + error = '' + hasMore = true + loadMoreCursor?: string + + // data + blocks: ActorDefs.ProfileView[] = [] + + constructor(public rootStore: RootStoreModel) { + makeAutoObservable( + this, + { + rootStore: false, + }, + {autoBind: true}, + ) + } + + get hasContent() { + return this.blocks.length > 0 + } + + get hasError() { + return this.error !== '' + } + + get isEmpty() { + return this.hasLoaded && !this.hasContent + } + + // public api + // = + + async refresh() { + return this.loadMore(true) + } + + loadMore = bundleAsync(async (replace: boolean = false) => { + if (!replace && !this.hasMore) { + return + } + this._xLoading(replace) + try { + const res = await this.rootStore.agent.app.bsky.graph.getBlocks({ + limit: PAGE_SIZE, + cursor: replace ? undefined : this.loadMoreCursor, + }) + if (replace) { + this._replaceAll(res) + } else { + this._appendAll(res) + } + this._xIdle() + } catch (e: any) { + this._xIdle(e) + } + }) + + // state transitions + // = + + _xLoading(isRefreshing = false) { + this.isLoading = true + this.isRefreshing = isRefreshing + this.error = '' + } + + _xIdle(err?: any) { + this.isLoading = false + this.isRefreshing = false + this.hasLoaded = true + this.error = cleanError(err) + if (err) { + this.rootStore.log.error('Failed to fetch user followers', err) + } + } + + // helper functions + // = + + _replaceAll(res: GetBlocks.Response) { + this.blocks = [] + this._appendAll(res) + } + + _appendAll(res: GetBlocks.Response) { + this.loadMoreCursor = res.data.cursor + this.hasMore = !!this.loadMoreCursor + this.blocks = this.blocks.concat(res.data.blocks) + } +} |