Refactor moderation to apply to accounts, profiles, and posts correctly (#548)
* Add ScreenHider component * Add blur attribute to UserAvatar and UserBanner * Remove dead suggested posts component and model * Bump @atproto/api@0.2.10 * Rework moderation tooling to give a more precise DSL * Add label mocks * Apply finer grained moderation controls * Refactor ProfileCard to just take the profile object * Apply moderation to user listings and banner * Apply moderation to notifications * Fix lint * Tune avatar & banner blur settings per platform * 1.24
This commit is contained in:
parent
51be8474db
commit
1d50ddb378
40 changed files with 1195 additions and 763 deletions
|
@ -10,6 +10,13 @@ import {RootStoreModel} from '../root-store'
|
|||
import * as apilib from 'lib/api/index'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {updateDataOptimistically} from 'lib/async/revertible'
|
||||
import {PostLabelInfo, PostModeration} from 'lib/labeling/types'
|
||||
import {
|
||||
getEmbedLabels,
|
||||
filterAccountLabels,
|
||||
filterProfileLabels,
|
||||
getPostModeration,
|
||||
} from 'lib/labeling/helpers'
|
||||
|
||||
export class PostThreadItemModel {
|
||||
// ui state
|
||||
|
@ -46,6 +53,21 @@ export class PostThreadItemModel {
|
|||
return this.rootStore.mutedThreads.uris.has(this.rootUri)
|
||||
}
|
||||
|
||||
get labelInfo(): PostLabelInfo {
|
||||
return {
|
||||
postLabels: (this.post.labels || []).concat(
|
||||
getEmbedLabels(this.post.embed),
|
||||
),
|
||||
accountLabels: filterAccountLabels(this.post.author.labels),
|
||||
profileLabels: filterProfileLabels(this.post.author.labels),
|
||||
isMuted: this.post.author.viewer?.muted || false,
|
||||
}
|
||||
}
|
||||
|
||||
get moderation(): PostModeration {
|
||||
return getPostModeration(this.rootStore, this.labelInfo)
|
||||
}
|
||||
|
||||
constructor(
|
||||
public rootStore: RootStoreModel,
|
||||
v: AppBskyFeedDefs.ThreadViewPost,
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
import {makeAutoObservable} from 'mobx'
|
||||
import {AppBskyFeedPost as Post} from '@atproto/api'
|
||||
import {AtUri} from '@atproto/api'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
|
||||
type RemoveIndex<T> = {
|
||||
[P in keyof T as string extends P
|
||||
? never
|
||||
: number extends P
|
||||
? never
|
||||
: P]: T[P]
|
||||
}
|
||||
export class PostModel implements RemoveIndex<Post.Record> {
|
||||
// state
|
||||
isLoading = false
|
||||
hasLoaded = false
|
||||
error = ''
|
||||
uri: string = ''
|
||||
|
||||
// data
|
||||
text: string = ''
|
||||
entities?: Post.Entity[]
|
||||
reply?: Post.ReplyRef
|
||||
createdAt: string = ''
|
||||
|
||||
constructor(public rootStore: RootStoreModel, uri: string) {
|
||||
makeAutoObservable(
|
||||
this,
|
||||
{
|
||||
rootStore: false,
|
||||
uri: false,
|
||||
},
|
||||
{autoBind: true},
|
||||
)
|
||||
this.uri = uri
|
||||
}
|
||||
|
||||
get hasContent() {
|
||||
return this.createdAt !== ''
|
||||
}
|
||||
|
||||
get hasError() {
|
||||
return this.error !== ''
|
||||
}
|
||||
|
||||
get isEmpty() {
|
||||
return this.hasLoaded && !this.hasContent
|
||||
}
|
||||
|
||||
get rootUri(): string {
|
||||
if (this.reply?.root.uri) {
|
||||
return this.reply.root.uri
|
||||
}
|
||||
return this.uri
|
||||
}
|
||||
|
||||
get isThreadMuted() {
|
||||
return this.rootStore.mutedThreads.uris.has(this.rootUri)
|
||||
}
|
||||
|
||||
// public api
|
||||
// =
|
||||
|
||||
async setup() {
|
||||
await this._load()
|
||||
}
|
||||
|
||||
async toggleThreadMute() {
|
||||
if (this.isThreadMuted) {
|
||||
this.rootStore.mutedThreads.uris.delete(this.rootUri)
|
||||
} else {
|
||||
this.rootStore.mutedThreads.uris.add(this.rootUri)
|
||||
}
|
||||
}
|
||||
|
||||
// state transitions
|
||||
// =
|
||||
|
||||
_xLoading() {
|
||||
this.isLoading = true
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
_xIdle(err?: any) {
|
||||
this.isLoading = false
|
||||
this.hasLoaded = true
|
||||
this.error = cleanError(err)
|
||||
if (err) {
|
||||
this.rootStore.log.error('Failed to fetch post', err)
|
||||
}
|
||||
}
|
||||
|
||||
// loader functions
|
||||
// =
|
||||
|
||||
async _load() {
|
||||
this._xLoading()
|
||||
try {
|
||||
const urip = new AtUri(this.uri)
|
||||
const res = await this.rootStore.agent.getPost({
|
||||
repo: urip.host,
|
||||
rkey: urip.rkey,
|
||||
})
|
||||
// TODO
|
||||
// if (!res.valid) {
|
||||
// throw new Error(res.error)
|
||||
// }
|
||||
this._replaceAll(res.value)
|
||||
this._xIdle()
|
||||
} catch (e: any) {
|
||||
this._xIdle(e)
|
||||
}
|
||||
}
|
||||
|
||||
_replaceAll(res: Post.Record) {
|
||||
this.text = res.text
|
||||
this.entities = res.entities
|
||||
this.reply = res.reply
|
||||
this.createdAt = res.createdAt
|
||||
}
|
||||
}
|
|
@ -10,6 +10,12 @@ import * as apilib from 'lib/api/index'
|
|||
import {cleanError} from 'lib/strings/errors'
|
||||
import {FollowState} from '../cache/my-follows'
|
||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||
import {ProfileLabelInfo, ProfileModeration} from 'lib/labeling/types'
|
||||
import {
|
||||
getProfileModeration,
|
||||
filterAccountLabels,
|
||||
filterProfileLabels,
|
||||
} from 'lib/labeling/helpers'
|
||||
|
||||
export const ACTOR_TYPE_USER = 'app.bsky.system.actorUser'
|
||||
|
||||
|
@ -75,6 +81,18 @@ export class ProfileModel {
|
|||
return this.hasLoaded && !this.hasContent
|
||||
}
|
||||
|
||||
get labelInfo(): ProfileLabelInfo {
|
||||
return {
|
||||
accountLabels: filterAccountLabels(this.labels),
|
||||
profileLabels: filterProfileLabels(this.labels),
|
||||
isMuted: this.viewer?.muted || false,
|
||||
}
|
||||
}
|
||||
|
||||
get moderation(): ProfileModeration {
|
||||
return getProfileModeration(this.rootStore, this.labelInfo)
|
||||
}
|
||||
|
||||
// public api
|
||||
// =
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue