From 15d0f2e90b219292c0ccb7e8ad802c405d798b3f Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Wed, 7 Feb 2024 21:26:28 -0800 Subject: [PATCH] Add (WIP) debug mod page --- bskyweb/cmd/bskyweb/server.go | 1 + src/Navigation.tsx | 6 + src/lib/moderation.ts | 17 +- src/lib/routes/types.ts | 1 + src/lib/strings/display-names.ts | 3 +- src/routes.ts | 1 + src/view/com/modals/ModerationDetails.tsx | 15 +- src/view/com/profile/ProfileCard.tsx | 6 +- src/view/com/util/moderation/ContentHider.tsx | 9 +- src/view/com/util/moderation/PostAlerts.tsx | 9 +- src/view/com/util/moderation/PostHider.tsx | 9 +- .../util/moderation/ProfileHeaderAlerts.tsx | 4 +- src/view/com/util/moderation/ScreenHider.tsx | 9 +- src/view/screens/DebugMod.tsx | 450 ++++++++++++++++++ 14 files changed, 523 insertions(+), 17 deletions(-) create mode 100644 src/view/screens/DebugMod.tsx diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index cd6487cf10..b78f70112c 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -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) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index e03b78a2ce..12d92ed465 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -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}} /> + DebugModScreen} + options={{title: title(msg`Moderation states`), requireAuth: true}} + /> LogScreen} diff --git a/src/lib/moderation.ts b/src/lib/moderation.ts index 53a3fbcd45..2c48aa1277 100644 --- a/src/lib/moderation.ts +++ b/src/lib/moderation.ts @@ -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( diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 64020d96ea..5360868be7 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -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 diff --git a/src/lib/strings/display-names.ts b/src/lib/strings/display-names.ts index 75383dd4fd..d157550009 100644 --- a/src/lib/strings/display-names.ts +++ b/src/lib/strings/display-names.ts @@ -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() diff --git a/src/routes.ts b/src/routes.ts index 7da64be018..d9aea05719 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -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', diff --git a/src/view/com/modals/ModerationDetails.tsx b/src/view/com/modals/ModerationDetails.tsx index f890d50dce..f053a07881 100644 --- a/src/view/com/modals/ModerationDetails.tsx +++ b/src/view/com/modals/ModerationDetails.tsx @@ -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 ( diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 266adc51de..da39f6e528 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -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({ )} {causes.map(cause => { - const desc = describeModerationCause(cause, 'account') + const desc = describeModerationCause(cause, 'account', labelStrings) return ( { diff --git a/src/view/com/util/moderation/PostHider.tsx b/src/view/com/util/moderation/PostHider.tsx index b1fa71d4ac..829f50cdf2 100644 --- a/src/view/com/util/moderation/PostHider.tsx +++ b/src/view/com/util/moderation/PostHider.tsx @@ -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 ? ( { diff --git a/src/view/com/util/moderation/ProfileHeaderAlerts.tsx b/src/view/com/util/moderation/ProfileHeaderAlerts.tsx index 0f07b679ba..de53dc1a30 100644 --- a/src/view/com/util/moderation/ProfileHeaderAlerts.tsx +++ b/src/view/com/util/moderation/ProfileHeaderAlerts.tsx @@ -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({ {causes.map(cause => { const isMute = cause.type === 'muted' - const desc = describeModerationCause(cause, 'account') + const desc = describeModerationCause(cause, 'account', labelStrings) return ( () 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 ( [key, 'hide' as LabelPreference]), + ), + }, + ], +} + +export const DebugModScreen = ({}: NativeStackScreenProps< + CommonNavigatorParams, + 'DebugMod' +>) => { + const t = useTheme() + const [label, setLabel] = React.useState([LABEL_VALUES[0]]) + const [target, setTarget] = React.useState(['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 ( + + +

Moderation states

+ +

Target

+ + + + Account + + + Profile + + + Post + + + Embed + + + + +

Label

+ + + {LABEL_VALUES.map(labelValue => ( + + + {labelValue} + + ))} + + + +

Post

+ + + + + + + +

+ Account{' '} +

in listing

+ + + + + + + + +

+ Account{' '} +

+ viewing directly +

+ + + + + + + + + +
+
+ ) +} + +function MockPost({ + label, + post, + moderation, +}: { + label: string + post: AppBskyFeedDefs.PostView + moderation: PostModeration +}) { + const t = useTheme() + return ( + + {' '} + + + + + +

Bob Robertson

+

+ @bob.bsky.social · 5m +

+
+ + + +

+ This is the body of the post. It's where the text goes. You get + the idea. +

+ + + + +

Bob Robertson

+

+ @bob.bsky.social · 5m +

+
+ +

Embedded content

+
+
+
+
+
+
+ ) +} + +function MockAccountCard({ + label, + profile, + moderation, +}: { + label: string + profile: AppBskyActorDefs.ProfileViewBasic + moderation: ProfileModeration +}) { + const t = useTheme() + return ( + + + +

+ {sanitizeDisplayName('Bob Robertson', moderation.profile)}{' '} +

+

@bob.bsky.social

+

Thought leader or something.

+ +
+
+ ) +} + +function MockAccountScreen({ + label, + profile, + moderation, +}: { + label: string + profile: AppBskyActorDefs.ProfileViewBasic + moderation: ProfileModeration +}) { + const t = useTheme() + return ( + + + + {/* + + + + + +

Bob Robertson

+

@bob.bsky.social

+

Thought leader or something.

+
+ */} +
+ ) +} + +function ModerationUIView({mod, label}: {mod: ModerationUI; label: string}) { + return ( + +

{label}:

+ + + + +
+ ) +} + +function Flag({v, label}: {v: boolean | undefined; label: string}) { + const t = useTheme() + return ( + + + {v && } + +

{label}

+
+ ) +}