Add (WIP) debug mod page
This commit is contained in:
@@ -196,6 +196,7 @@ func serve(cctx *cli.Context) error {
|
||||
e.GET("/settings/threads", server.WebGeneric)
|
||||
e.GET("/settings/external-embeds", server.WebGeneric)
|
||||
e.GET("/sys/debug", server.WebGeneric)
|
||||
e.GET("/sys/debug-mod", server.WebGeneric)
|
||||
e.GET("/sys/log", server.WebGeneric)
|
||||
e.GET("/support", server.WebGeneric)
|
||||
e.GET("/support/privacy", server.WebGeneric)
|
||||
|
||||
@@ -62,6 +62,7 @@ import {PostThreadScreen} from './view/screens/PostThread'
|
||||
import {PostLikedByScreen} from './view/screens/PostLikedBy'
|
||||
import {PostRepostedByScreen} from './view/screens/PostRepostedBy'
|
||||
import {Storybook} from './view/screens/Storybook'
|
||||
import {DebugModScreen} from './view/screens/DebugMod'
|
||||
import {LogScreen} from './view/screens/Log'
|
||||
import {SupportScreen} from './view/screens/Support'
|
||||
import {PrivacyPolicyScreen} from './view/screens/PrivacyPolicy'
|
||||
@@ -208,6 +209,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
|
||||
getComponent={() => Storybook}
|
||||
options={{title: title(msg`Storybook`), requireAuth: true}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="DebugMod"
|
||||
getComponent={() => DebugModScreen}
|
||||
options={{title: title(msg`Moderation states`), requireAuth: true}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Log"
|
||||
getComponent={() => LogScreen}
|
||||
|
||||
+16
-1
@@ -17,6 +17,7 @@ export interface ModerationCauseDescription {
|
||||
export function describeModerationCause(
|
||||
cause: ModerationCause | undefined,
|
||||
context: 'account' | 'content',
|
||||
labelStrings: LabelStrings,
|
||||
): ModerationCauseDescription {
|
||||
// TODO localize
|
||||
if (!cause) {
|
||||
@@ -77,7 +78,21 @@ export function describeModerationCause(
|
||||
description: `You have hidden this post`,
|
||||
}
|
||||
}
|
||||
return cause.labelDef.strings[context].en
|
||||
if (cause.labelDef.id in labelStrings) {
|
||||
const strings = labelStrings[cause.labelDef.id as keyof typeof LABELS]
|
||||
return {
|
||||
name: context === 'account' ? strings.account.name : strings.content.name,
|
||||
description:
|
||||
context === 'account'
|
||||
? strings.account.description
|
||||
: strings.content.description,
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: 'TODO',
|
||||
description: `TODo`,
|
||||
}
|
||||
// return cause.labelDef.strings[context].en
|
||||
}
|
||||
|
||||
export function getProfileModerationCauses(
|
||||
|
||||
@@ -23,6 +23,7 @@ export type CommonNavigatorParams = {
|
||||
ProfileFeedLikedBy: {name: string; rkey: string}
|
||||
ProfileModservice: {name: string}
|
||||
Debug: undefined
|
||||
DebugMod: undefined
|
||||
Log: undefined
|
||||
Support: undefined
|
||||
PrivacyPolicy: undefined
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {ModerationUI} from '@atproto/api'
|
||||
import {describeModerationCause} from '../moderation'
|
||||
|
||||
// \u2705 = ✅
|
||||
// \u2713 = ✓
|
||||
@@ -14,7 +13,7 @@ export function sanitizeDisplayName(
|
||||
moderation?: ModerationUI,
|
||||
): string {
|
||||
if (moderation?.blur) {
|
||||
return `⚠${describeModerationCause(moderation.cause, 'account').name}`
|
||||
return `⚠ Display name hidden`
|
||||
}
|
||||
if (typeof str === 'string') {
|
||||
return str.replace(CHECK_MARKS_RE, '').replace(CONTROL_CHARS_RE, '').trim()
|
||||
|
||||
@@ -23,6 +23,7 @@ export const router = new Router({
|
||||
ProfileFeedLikedBy: '/profile/:name/feed/:rkey/liked-by',
|
||||
ProfileModservice: '/profile/:name/modservice',
|
||||
Debug: '/sys/debug',
|
||||
DebugMod: '/sys/debug-mod',
|
||||
Log: '/sys/log',
|
||||
AppPasswords: '/settings/app-passwords',
|
||||
PreferencesHomeFeed: '/settings/home-feed',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {ModerationUI} from '@atproto/api'
|
||||
import {ModerationUI, LABELS} from '@atproto/api'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {s} from 'lib/styles'
|
||||
import {Text} from '../util/text/Text'
|
||||
@@ -12,6 +12,7 @@ import {Button} from '../util/forms/Button'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLabelStrings} from '#/lib/moderation'
|
||||
|
||||
export const snapPoints = [300]
|
||||
|
||||
@@ -26,6 +27,7 @@ export function Component({
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const labelStrings = useLabelStrings()
|
||||
|
||||
let name
|
||||
let description
|
||||
@@ -86,9 +88,16 @@ export function Component({
|
||||
name = _(msg`Account Muted`)
|
||||
description = _(msg`You have muted this user.`)
|
||||
}
|
||||
} else if (moderation.cause.labelDef.id in labelStrings) {
|
||||
name =
|
||||
labelStrings[moderation.cause.labelDef.id as keyof typeof LABELS][context]
|
||||
.name
|
||||
description =
|
||||
labelStrings[moderation.cause.labelDef.id as keyof typeof LABELS][context]
|
||||
.description
|
||||
} else {
|
||||
name = moderation.cause.labelDef.strings[context].en.name
|
||||
description = moderation.cause.labelDef.strings[context].en.description
|
||||
name = 'TODO'
|
||||
description = 'TODO'
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
describeModerationCause,
|
||||
getProfileModerationCauses,
|
||||
getModerationCauseKey,
|
||||
useLabelStrings,
|
||||
} from 'lib/moderation'
|
||||
import {Shadow} from '#/state/cache/types'
|
||||
import {useModerationOpts} from '#/state/queries/preferences'
|
||||
@@ -119,7 +120,7 @@ export function ProfileCard({
|
||||
)
|
||||
}
|
||||
|
||||
function ProfileCardPills({
|
||||
export function ProfileCardPills({
|
||||
followedBy,
|
||||
moderation,
|
||||
}: {
|
||||
@@ -127,6 +128,7 @@ function ProfileCardPills({
|
||||
moderation: ProfileModeration
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const labelStrings = useLabelStrings()
|
||||
|
||||
const causes = getProfileModerationCauses(moderation)
|
||||
if (!followedBy && !causes.length) {
|
||||
@@ -143,7 +145,7 @@ function ProfileCardPills({
|
||||
</View>
|
||||
)}
|
||||
{causes.map(cause => {
|
||||
const desc = describeModerationCause(cause, 'account')
|
||||
const desc = describeModerationCause(cause, 'account', labelStrings)
|
||||
return (
|
||||
<View
|
||||
style={[s.mt5, pal.btn, styles.pill]}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {ModerationUI, PostModeration} from '@atproto/api'
|
||||
import {Text} from '../text/Text'
|
||||
import {ShieldExclamation} from 'lib/icons'
|
||||
import {describeModerationCause} from 'lib/moderation'
|
||||
import {describeModerationCause, useLabelStrings} from 'lib/moderation'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
@@ -33,6 +33,7 @@ export function ContentHider({
|
||||
const {_} = useLingui()
|
||||
const [override, setOverride] = React.useState(false)
|
||||
const {openModal} = useModalControls()
|
||||
const labelStrings = useLabelStrings()
|
||||
|
||||
if (
|
||||
!moderation.blur ||
|
||||
@@ -47,7 +48,11 @@ export function ContentHider({
|
||||
}
|
||||
|
||||
const isMute = moderation.cause?.type === 'muted'
|
||||
const desc = describeModerationCause(moderation.cause, 'content')
|
||||
const desc = describeModerationCause(
|
||||
moderation.cause,
|
||||
'content',
|
||||
labelStrings,
|
||||
)
|
||||
return (
|
||||
<View testID={testID} style={[styles.outer, style]}>
|
||||
<Pressable
|
||||
|
||||
@@ -4,7 +4,7 @@ import {ModerationUI} from '@atproto/api'
|
||||
import {Text} from '../text/Text'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {ShieldExclamation} from 'lib/icons'
|
||||
import {describeModerationCause} from 'lib/moderation'
|
||||
import {describeModerationCause, useLabelStrings} from 'lib/moderation'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
@@ -20,13 +20,18 @@ export function PostAlerts({
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {openModal} = useModalControls()
|
||||
const labelStrings = useLabelStrings()
|
||||
|
||||
const shouldAlert = !!moderation.cause && moderation.alert
|
||||
if (!shouldAlert) {
|
||||
return null
|
||||
}
|
||||
|
||||
const desc = describeModerationCause(moderation.cause, 'content')
|
||||
const desc = describeModerationCause(
|
||||
moderation.cause,
|
||||
'content',
|
||||
labelStrings,
|
||||
)
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {Link} from '../Link'
|
||||
import {Text} from '../text/Text'
|
||||
import {addStyle} from 'lib/styles'
|
||||
import {describeModerationCause} from 'lib/moderation'
|
||||
import {describeModerationCause, useLabelStrings} from 'lib/moderation'
|
||||
import {ShieldExclamation} from 'lib/icons'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
@@ -32,6 +32,7 @@ export function PostHider({
|
||||
const {_} = useLingui()
|
||||
const [override, setOverride] = React.useState(false)
|
||||
const {openModal} = useModalControls()
|
||||
const labelStrings = useLabelStrings()
|
||||
|
||||
if (!moderation.blur) {
|
||||
return (
|
||||
@@ -48,7 +49,11 @@ export function PostHider({
|
||||
}
|
||||
|
||||
const isMute = moderation.cause?.type === 'muted'
|
||||
const desc = describeModerationCause(moderation.cause, 'content')
|
||||
const desc = describeModerationCause(
|
||||
moderation.cause,
|
||||
'content',
|
||||
labelStrings,
|
||||
)
|
||||
return !override ? (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {ShieldExclamation} from 'lib/icons'
|
||||
import {
|
||||
describeModerationCause,
|
||||
getProfileModerationCauses,
|
||||
useLabelStrings,
|
||||
} from 'lib/moderation'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -23,6 +24,7 @@ export function ProfileHeaderAlerts({
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {openModal} = useModalControls()
|
||||
const labelStrings = useLabelStrings()
|
||||
|
||||
const causes = getProfileModerationCauses(moderation)
|
||||
if (!causes.length) {
|
||||
@@ -33,7 +35,7 @@ export function ProfileHeaderAlerts({
|
||||
<View style={styles.grid}>
|
||||
{causes.map(cause => {
|
||||
const isMute = cause.type === 'muted'
|
||||
const desc = describeModerationCause(cause, 'account')
|
||||
const desc = describeModerationCause(cause, 'account', labelStrings)
|
||||
return (
|
||||
<Pressable
|
||||
testID="profileHeaderAlert"
|
||||
|
||||
@@ -17,7 +17,7 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {Text} from '../text/Text'
|
||||
import {Button} from '../forms/Button'
|
||||
import {describeModerationCause} from 'lib/moderation'
|
||||
import {describeModerationCause, useLabelStrings} from 'lib/moderation'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
@@ -45,6 +45,7 @@ export function ScreenHider({
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const {openModal} = useModalControls()
|
||||
const labelStrings = useLabelStrings()
|
||||
|
||||
if (!moderation.blur || override) {
|
||||
return (
|
||||
@@ -57,7 +58,11 @@ export function ScreenHider({
|
||||
const isNoPwi =
|
||||
moderation.cause?.type === 'label' &&
|
||||
moderation.cause?.labelDef.id === '!no-unauthenticated'
|
||||
const desc = describeModerationCause(moderation.cause, 'account')
|
||||
const desc = describeModerationCause(
|
||||
moderation.cause,
|
||||
'account',
|
||||
labelStrings,
|
||||
)
|
||||
return (
|
||||
<CenteredView
|
||||
style={[styles.container, pal.view, containerStyle]}
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
import React from 'react'
|
||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||
import {View} from 'react-native'
|
||||
import {
|
||||
LABELS,
|
||||
LABEL_GROUPS,
|
||||
mock,
|
||||
moderatePost,
|
||||
moderateProfile,
|
||||
PostModeration,
|
||||
ProfileModeration,
|
||||
ModerationUI,
|
||||
LabelPreference,
|
||||
AppBskyActorDefs,
|
||||
AppBskyFeedDefs,
|
||||
} from '@atproto/api'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||
import {H1, H3, P} from '#/components/Typography'
|
||||
import {useLabelStrings} from '#/lib/moderation'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import * as ToggleButton from '#/components/forms/ToggleButton'
|
||||
import {UserAvatar} from '../com/util/UserAvatar'
|
||||
|
||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||
import {PostHider} from '../com/util/moderation/PostHider'
|
||||
import {PostAlerts} from '../com/util/moderation/PostAlerts'
|
||||
import {ContentHider} from '../com/util/moderation/ContentHider'
|
||||
import {ProfileHeader} from '../com/profile/ProfileHeader'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {ProfileCardPills} from '../com/profile/ProfileCard'
|
||||
|
||||
const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys(
|
||||
LABELS,
|
||||
) as (keyof typeof LABELS)[]
|
||||
|
||||
const MOCK_MOD_OPTS = {
|
||||
userDid: 'at://did:web:alice',
|
||||
adultContentEnabled: true,
|
||||
labelGroups: {},
|
||||
labelers: [
|
||||
{
|
||||
labeler: {did: 'did:plc:fake-labeler'},
|
||||
labelGroups: Object.fromEntries(
|
||||
Object.keys(LABEL_GROUPS).map(key => [key, 'hide' as LabelPreference]),
|
||||
),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const DebugModScreen = ({}: NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
'DebugMod'
|
||||
>) => {
|
||||
const t = useTheme()
|
||||
const [label, setLabel] = React.useState<string[]>([LABEL_VALUES[0]])
|
||||
const [target, setTarget] = React.useState<string[]>(['account'])
|
||||
const labelStrings = useLabelStrings()
|
||||
|
||||
const profile = React.useMemo(() => {
|
||||
const mockedProfile = mock.profileViewBasic({
|
||||
handle: `bob.test`,
|
||||
displayName: 'Bob Robertson',
|
||||
labels:
|
||||
target[0] === 'account'
|
||||
? [
|
||||
mock.label({
|
||||
val: label[0],
|
||||
uri: `at://did:web:bob/`,
|
||||
}),
|
||||
]
|
||||
: target[0] === 'profile'
|
||||
? [
|
||||
mock.label({
|
||||
val: label[0],
|
||||
uri: `at://did:web:bob/app.bsky.actor.profile/self`,
|
||||
}),
|
||||
]
|
||||
: undefined,
|
||||
viewer: mock.actorViewerState({
|
||||
muted: false,
|
||||
mutedByList: undefined,
|
||||
blockedBy: undefined,
|
||||
blocking: undefined,
|
||||
blockingByList: undefined,
|
||||
}),
|
||||
})
|
||||
mockedProfile.avatar = 'https://bsky.social/about/images/favicon-32x32.png'
|
||||
mockedProfile.banner =
|
||||
'https://bsky.social/about/images/social-card-default-gradient.png'
|
||||
return mockedProfile
|
||||
}, [target, label])
|
||||
|
||||
const post = React.useMemo(() => {
|
||||
return mock.postView({
|
||||
record: mock.post({
|
||||
text: "This is the body of the post. It's where the text goes. You get the idea.",
|
||||
}),
|
||||
author: profile,
|
||||
labels:
|
||||
target[0] === 'post'
|
||||
? [
|
||||
mock.label({
|
||||
val: label[0],
|
||||
uri: `at://bob.test/app.bsky.feed.post/fake`,
|
||||
}),
|
||||
]
|
||||
: undefined,
|
||||
embed: mock.embedRecordView({
|
||||
record: mock.post({
|
||||
text: 'Embed',
|
||||
}),
|
||||
labels:
|
||||
target[0] === 'embed'
|
||||
? [
|
||||
mock.label({
|
||||
val: label[0],
|
||||
uri: `at://bob.test/app.bsky.feed.post/fake`,
|
||||
}),
|
||||
]
|
||||
: undefined,
|
||||
author: profile,
|
||||
}),
|
||||
})
|
||||
}, [label, target, profile])
|
||||
|
||||
const profileModeration = React.useMemo(
|
||||
() => moderateProfile(profile, MOCK_MOD_OPTS),
|
||||
[profile],
|
||||
)
|
||||
const postModeration = React.useMemo(
|
||||
() => moderatePost(post, MOCK_MOD_OPTS),
|
||||
[post],
|
||||
)
|
||||
|
||||
console.log(post, profile)
|
||||
console.log(profileModeration, postModeration)
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<CenteredView style={[t.atoms.bg, a.px_lg, a.py_lg]}>
|
||||
<H1 style={a.pb_lg}>Moderation states</H1>
|
||||
|
||||
<H3 style={a.pb_md}>Target</H3>
|
||||
<View style={a.pb_2xl}>
|
||||
<ToggleButton.Group
|
||||
label="Preferences"
|
||||
values={target}
|
||||
onChange={setTarget}>
|
||||
<ToggleButton.Button name="account" label="Account">
|
||||
Account
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="profile" label="Profile">
|
||||
Profile
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="post" label="Post">
|
||||
Post
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="embed" label="Embed">
|
||||
Embed
|
||||
</ToggleButton.Button>
|
||||
</ToggleButton.Group>
|
||||
</View>
|
||||
|
||||
<H3 style={a.pb_md}>Label</H3>
|
||||
<Toggle.Group
|
||||
label="Toggle"
|
||||
type="radio"
|
||||
values={label}
|
||||
onChange={setLabel}>
|
||||
<View style={[a.flex_row, a.gap_md, a.flex_wrap, a.pb_2xl]}>
|
||||
{LABEL_VALUES.map(labelValue => (
|
||||
<Toggle.Item
|
||||
key={labelValue}
|
||||
name={labelValue}
|
||||
label={labelStrings[labelValue].general.name}>
|
||||
<Toggle.Radio />
|
||||
<Toggle.Label>{labelValue}</Toggle.Label>
|
||||
</Toggle.Item>
|
||||
))}
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
|
||||
<H3 style={a.pb_md}>Post</H3>
|
||||
<MockPost label={label[0]} post={post} moderation={postModeration} />
|
||||
<View
|
||||
style={[
|
||||
t.atoms.bg_contrast_50,
|
||||
a.px_md,
|
||||
a.py_sm,
|
||||
a.rounded_sm,
|
||||
a.flex_col,
|
||||
a.gap_xs,
|
||||
a.mb_2xl,
|
||||
]}>
|
||||
<ModerationUIView mod={postModeration.avatar} label="Avatar" />
|
||||
<ModerationUIView mod={postModeration.content} label="Content" />
|
||||
<ModerationUIView mod={postModeration.embed} label="Embed" />
|
||||
</View>
|
||||
|
||||
<H3 style={a.pb_md}>
|
||||
Account{' '}
|
||||
<H3 style={[t.atoms.text_contrast_500, a.text_lg]}>in listing</H3>
|
||||
</H3>
|
||||
<MockAccountCard
|
||||
label={label[0]}
|
||||
profile={profile}
|
||||
moderation={profileModeration}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
t.atoms.bg_contrast_50,
|
||||
a.px_md,
|
||||
a.py_sm,
|
||||
a.rounded_sm,
|
||||
a.flex_col,
|
||||
a.gap_xs,
|
||||
a.mb_2xl,
|
||||
]}>
|
||||
<ModerationUIView mod={profileModeration.avatar} label="Avatar" />
|
||||
<ModerationUIView mod={profileModeration.account} label="Account" />
|
||||
<ModerationUIView mod={profileModeration.profile} label="Profile" />
|
||||
</View>
|
||||
|
||||
<H3 style={a.pb_md}>
|
||||
Account{' '}
|
||||
<H3 style={[t.atoms.text_contrast_500, a.text_lg]}>
|
||||
viewing directly
|
||||
</H3>
|
||||
</H3>
|
||||
<MockAccountScreen
|
||||
label={label[0]}
|
||||
profile={profile}
|
||||
moderation={profileModeration}
|
||||
postModeration={postModeration}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
t.atoms.bg_contrast_50,
|
||||
a.px_md,
|
||||
a.py_sm,
|
||||
a.rounded_sm,
|
||||
a.flex_col,
|
||||
a.gap_xs,
|
||||
a.mb_2xl,
|
||||
]}>
|
||||
<ModerationUIView mod={profileModeration.avatar} label="Avatar" />
|
||||
<ModerationUIView mod={profileModeration.account} label="Account" />
|
||||
<ModerationUIView mod={profileModeration.profile} label="Profile" />
|
||||
</View>
|
||||
|
||||
<View style={{height: 400}} />
|
||||
</CenteredView>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
function MockPost({
|
||||
label,
|
||||
post,
|
||||
moderation,
|
||||
}: {
|
||||
label: string
|
||||
post: AppBskyFeedDefs.PostView
|
||||
moderation: PostModeration
|
||||
}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View style={[t.atoms.border, a.border, a.rounded_md, a.mb_md, a.p_2xs]}>
|
||||
{' '}
|
||||
<PostHider
|
||||
key={label}
|
||||
href="#"
|
||||
moderation={moderation.content}
|
||||
iconSize={38}
|
||||
iconStyles={{marginLeft: 2, marginRight: 2}}
|
||||
style={[a.px_lg, a.py_lg, a.rounded_md]}>
|
||||
<View style={[a.flex_row, a.gap_md]}>
|
||||
<UserAvatar
|
||||
size={64}
|
||||
avatar={post.author.avatar}
|
||||
moderation={moderation.avatar}
|
||||
/>
|
||||
<View style={[a.flex_1]}>
|
||||
<View style={[a.flex_row]}>
|
||||
<P style={[a.font_bold]}>Bob Robertson </P>
|
||||
<P style={t.atoms.text_contrast_500}>
|
||||
@bob.bsky.social · 5m
|
||||
</P>
|
||||
</View>
|
||||
|
||||
<PostAlerts moderation={moderation.content} />
|
||||
|
||||
<P style={a.mb_lg}>
|
||||
This is the body of the post. It's where the text goes. You get
|
||||
the idea.
|
||||
</P>
|
||||
|
||||
<ContentHider
|
||||
moderation={moderation.embed}
|
||||
moderationDecisions={moderation.decisions}>
|
||||
<View
|
||||
style={[
|
||||
t.atoms.border,
|
||||
a.border,
|
||||
a.rounded_md,
|
||||
a.flex_col,
|
||||
a.gap_xs,
|
||||
a.px_xl,
|
||||
a.py_xl,
|
||||
]}>
|
||||
<View style={[a.flex_row]}>
|
||||
<P style={[a.font_bold]}>Bob Robertson </P>
|
||||
<P style={t.atoms.text_contrast_500}>
|
||||
@bob.bsky.social · 5m
|
||||
</P>
|
||||
</View>
|
||||
<PostAlerts moderation={moderation.embed} />
|
||||
<P>Embedded content</P>
|
||||
</View>
|
||||
</ContentHider>
|
||||
</View>
|
||||
</View>
|
||||
</PostHider>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function MockAccountCard({
|
||||
label,
|
||||
profile,
|
||||
moderation,
|
||||
}: {
|
||||
label: string
|
||||
profile: AppBskyActorDefs.ProfileViewBasic
|
||||
moderation: ProfileModeration
|
||||
}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View
|
||||
key={label}
|
||||
style={[
|
||||
t.atoms.border,
|
||||
a.border,
|
||||
a.rounded_md,
|
||||
a.flex_row,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
a.mb_md,
|
||||
]}>
|
||||
<UserAvatar
|
||||
size={64}
|
||||
avatar={profile.avatar}
|
||||
moderation={moderation.avatar}
|
||||
/>
|
||||
<View style={[a.flex_1]}>
|
||||
<P style={[a.font_bold]}>
|
||||
{sanitizeDisplayName('Bob Robertson', moderation.profile)}{' '}
|
||||
</P>
|
||||
<P style={t.atoms.text_contrast_500}>@bob.bsky.social</P>
|
||||
<P>Thought leader or something.</P>
|
||||
<ProfileCardPills followedBy={false} moderation={moderation} />
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function MockAccountScreen({
|
||||
label,
|
||||
profile,
|
||||
moderation,
|
||||
}: {
|
||||
label: string
|
||||
profile: AppBskyActorDefs.ProfileViewBasic
|
||||
moderation: ProfileModeration
|
||||
}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View key={label} style={[t.atoms.border, a.border, a.mb_md]}>
|
||||
<ProfileHeader
|
||||
// @ts-ignore ProfileViewBasic is close enough -prf
|
||||
profile={profile}
|
||||
moderation={moderation}
|
||||
/>
|
||||
|
||||
{/*
|
||||
<UserBanner />
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
t.atoms.bg,
|
||||
a.rounded_full,
|
||||
{top: 100, left: 10, width: 100, height: 100, padding: 2},
|
||||
]}>
|
||||
<UserAvatar size={96} moderation={moderation.avatar} />
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
a.pb_2xl,
|
||||
a.px_xl,
|
||||
t.atoms.border,
|
||||
a.border_b,
|
||||
{paddingTop: 60},
|
||||
]}>
|
||||
<H3>Bob Robertson</H3>
|
||||
<P style={t.atoms.text_contrast_500}>@bob.bsky.social</P>
|
||||
<P>Thought leader or something.</P>
|
||||
</View>
|
||||
*/}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function ModerationUIView({mod, label}: {mod: ModerationUI; label: string}) {
|
||||
return (
|
||||
<View style={[a.flex_row, a.gap_md]}>
|
||||
<P style={[a.font_bold, a.text_xs, {width: 60}]}>{label}:</P>
|
||||
<Flag v={mod.filter} label="Filter" />
|
||||
<Flag v={mod.blur} label="Blur" />
|
||||
<Flag v={mod.alert} label="Alert" />
|
||||
<Flag v={mod.noOverride} label="No-override" />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function Flag({v, label}: {v: boolean | undefined; label: string}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<View
|
||||
style={[
|
||||
a.justify_center,
|
||||
a.align_center,
|
||||
a.rounded_xs,
|
||||
a.border,
|
||||
t.atoms.border,
|
||||
{
|
||||
backgroundColor: v ? t.palette.black : t.palette.white,
|
||||
width: 14,
|
||||
height: 14,
|
||||
},
|
||||
]}>
|
||||
{v && <Check size="xs" fill={t.palette.white} />}
|
||||
</View>
|
||||
<P style={a.text_xs}>{label}</P>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user