diff --git a/src/components/dialogs/LabelsOnMe/index.tsx b/src/components/dialogs/LabelsOnMe/index.tsx new file mode 100644 index 0000000000..efc8019bda --- /dev/null +++ b/src/components/dialogs/LabelsOnMe/index.tsx @@ -0,0 +1,92 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {ComAtprotoLabelDefs} from '@atproto/api' + +import {atoms as a, useBreakpoints} from '#/alf' +import {Text} from '#/components/Typography' +import * as Dialog from '#/components/Dialog' +import {GlobalDialogProps} from '#/components/dialogs' +import {Button} from '#/components/Button' + +type Subject = + | { + uri: string + cid: string + } + | { + did: string + } + +export interface LabelsOnMeDialogProps { + subject: Subject + labels: ComAtprotoLabelDefs.Label[] +} + +export function LabelsOnMeDialog({ + params, + cleanup, +}: GlobalDialogProps) { + const {_} = useLingui() + const control = Dialog.useDialogControl() + const {gtMobile} = useBreakpoints() + const {subject, labels} = params + const isAccount = 'did' in subject + + // REQUIRED CLEANUP + const onClose = React.useCallback(() => cleanup(), [cleanup]) + + // TODO + // const submit = async () => { + // try { + // const $type = !isAccountReport + // ? 'com.atproto.repo.strongRef' + // : 'com.atproto.admin.defs#repoRef' + // await getAgent().createModerationReport({ + // reasonType: ComAtprotoModerationDefs.REASONAPPEAL, + // subject: { + // $type, + // ...props, + // }, + // reason: details, + // }) + // Toast.show(_(msg`We'll look into your appeal promptly.`)) + // } finally { + // closeModal() + // } + // } + + return ( + + + + + + Labels on my {isAccount ? 'account' : 'content'} + + + + You may appeal these labels if you feel they were placed in error. + + + {labels.map(label => ( + {label.val} TODO + ))} + + + + + + ) +} diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index 0b73ff7e04..c39d72a8c3 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -36,16 +36,6 @@ export type ReportModal = { | {did: string} ) -export type AppealLabelModal = { - name: 'appeal-label' -} & ( - | { - uri: string - cid: string - } - | {did: string} -) - export interface CreateOrEditListModal { name: 'create-or-edit-list' purpose?: string @@ -198,7 +188,6 @@ export type Modal = // Moderation | ReportModal - | AppealLabelModal // Lists | CreateOrEditListModal diff --git a/src/view/com/modals/AppealLabel.tsx b/src/view/com/modals/AppealLabel.tsx deleted file mode 100644 index b0aaaf6255..0000000000 --- a/src/view/com/modals/AppealLabel.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React, {useState} from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {ComAtprotoModerationDefs} from '@atproto/api' -import {ScrollView, TextInput} from './util' -import {Text} from '../util/text/Text' -import {s, colors} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' -import {CharProgress} from '../composer/char-progress/CharProgress' -import {getAgent} from '#/state/session' -import * as Toast from '../util/Toast' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' - -export const snapPoints = ['40%'] - -type ReportComponentProps = - | { - uri: string - cid: string - } - | { - did: string - } - -export function Component(props: ReportComponentProps) { - const pal = usePalette('default') - const [details, setDetails] = useState('') - const {_} = useLingui() - const {closeModal} = useModalControls() - const {isMobile} = useWebMediaQueries() - const isAccountReport = 'did' in props - - const submit = async () => { - try { - const $type = !isAccountReport - ? 'com.atproto.repo.strongRef' - : 'com.atproto.admin.defs#repoRef' - await getAgent().createModerationReport({ - reasonType: ComAtprotoModerationDefs.REASONAPPEAL, - subject: { - $type, - ...props, - }, - reason: details, - }) - Toast.show(_(msg`We'll look into your appeal promptly.`)) - } finally { - closeModal() - } - } - - return ( - - - Appeal Content Warning - - - - - - - - - - - - - Submit - - - - - ) -} - -const styles = StyleSheet.create({ - detailsInputContainer: { - borderRadius: 8, - marginBottom: 8, - }, - detailsInput: { - paddingHorizontal: 12, - paddingTop: 12, - paddingBottom: 12, - borderRadius: 8, - minHeight: 100, - fontSize: 16, - }, - detailsInputBottomBar: { - alignSelf: 'flex-end', - }, - charCounter: { - flexDirection: 'row', - alignItems: 'center', - paddingRight: 10, - paddingBottom: 8, - }, - btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - borderRadius: 32, - padding: 14, - backgroundColor: colors.blue3, - }, -}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 8332e614ae..5664b941c7 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -17,7 +17,6 @@ import * as ListAddUserModal from './ListAddRemoveUsers' import * as AltImageModal from './AltImage' import * as EditImageModal from './AltImage' import * as ReportModal from './report/Modal' -import * as AppealLabelModal from './AppealLabel' import * as DeleteAccountModal from './DeleteAccount' import * as ChangeHandleModal from './ChangeHandle' import * as WaitlistModal from './Waitlist' @@ -75,9 +74,6 @@ export function ModalsContainer() { } else if (activeModal?.name === 'report') { snapPoints = ReportModal.snapPoints element = - } else if (activeModal?.name === 'appeal-label') { - snapPoints = AppealLabelModal.snapPoints - element = } else if (activeModal?.name === 'create-or-edit-list') { snapPoints = CreateOrEditListModal.snapPoints element = diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 4d94d74655..10966364e6 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -10,7 +10,6 @@ import type {Modal as ModalIface} from '#/state/modals' import * as ConfirmModal from './Confirm' import * as EditProfileModal from './EditProfile' import * as ReportModal from './report/Modal' -import * as AppealLabelModal from './AppealLabel' import * as CreateOrEditListModal from './CreateOrEditList' import * as UserAddRemoveLists from './UserAddRemoveLists' import * as ListAddUserModal from './ListAddRemoveUsers' @@ -84,8 +83,6 @@ function Modal({modal}: {modal: ModalIface}) { element = } else if (modal.name === 'report') { element = - } else if (modal.name === 'appeal-label') { - element = } else if (modal.name === 'create-or-edit-list') { element = } else if (modal.name === 'user-add-remove-lists') { diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 194c480ddb..9fa4420738 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -26,6 +26,7 @@ import {PostCtrls} from '../util/post-ctrls/PostCtrls' import {PostHider} from '../util/moderation/PostHider' import {ContentHider} from '../util/moderation/ContentHider' import {PostAlerts} from '../util/moderation/PostAlerts' +import {LabelsOnMyPost} from '../util/moderation/LabelsOnMe' import {PostSandboxWarning} from '../util/PostSandboxWarning' import {ErrorMessage} from '../util/error/ErrorMessage' import {usePalette} from 'lib/hooks/usePalette' @@ -286,6 +287,7 @@ let PostThreadItemLoaded = ({ )} + + )} + )} + @@ -324,12 +323,10 @@ let PostContent = ({ moderation, richText, postEmbed, - postAuthor, }: { moderation: ModerationDecision richText: RichTextAPI postEmbed: AppBskyFeedDefs.PostView['embed'] - postAuthor: AppBskyFeedDefs.PostView['author'] }): React.ReactNode => { const pal = usePalette('default') const {_} = useLingui() diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index de1b3decea..1113dff282 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -54,7 +54,7 @@ import {logger} from '#/logger' import {useSession} from '#/state/session' import {Shadow} from '#/state/cache/types' import {useRequireAuth} from '#/state/session' -import {LabelInfo} from '../util/moderation/LabelInfo' +import {LabelsOnMe} from '../util/moderation/LabelsOnMe' import {useProfileShadow} from 'state/cache/profile-shadow' import * as ModerationServiceCard from '#/components/ModerationServiceCard' import {getModerationServiceTitle} from '#/lib/moderation' @@ -635,7 +635,7 @@ let ProfileHeader = ({ )} {isMe && ( - + )} - showAppealLabelItem?: boolean }): React.ReactNode => { const {hasSession, currentAccount} = useSession() const theme = useTheme() @@ -255,23 +253,6 @@ let PostDropdownBtn = ({ web: ['far', 'trash-can'], }, }, - showAppealLabelItem && { - label: 'separator', - }, - showAppealLabelItem && { - label: _(msg`Appeal content warning`), - onPress() { - openModal({name: 'appeal-label', uri: postUri, cid: postCid}) - }, - testID: 'postDropdownAppealBtn', - icon: { - ios: { - name: 'exclamationmark.triangle', - }, - android: 'ic_menu_report_image', - web: 'circle-exclamation', - }, - }, ].filter(Boolean) as NativeDropdownItem[] return ( diff --git a/src/view/com/util/moderation/LabelInfo.tsx b/src/view/com/util/moderation/LabelInfo.tsx deleted file mode 100644 index 970338752c..0000000000 --- a/src/view/com/util/moderation/LabelInfo.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react' -import {Pressable, StyleProp, View, ViewStyle} from 'react-native' -import {ComAtprotoLabelDefs} from '@atproto/api' -import {Text} from '../text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' - -export function LabelInfo({ - details, - labels, - style, -}: { - details: {did: string} | {uri: string; cid: string} - labels: ComAtprotoLabelDefs.Label[] | undefined - style?: StyleProp -}) { - const pal = usePalette('default') - const {_} = useLingui() - const {openModal} = useModalControls() - - if (!labels) { - return null - } - labels = labels.filter(l => !l.val.startsWith('!')) - if (!labels.length) { - return null - } - - return ( - - - - A content warning has been applied to this{' '} - {'did' in details ? 'account' : 'post'}. - {' '} - - openModal({name: 'appeal-label', ...details})}> - - Appeal this decision. - - - - ) -} diff --git a/src/view/com/util/moderation/LabelsOnMe.tsx b/src/view/com/util/moderation/LabelsOnMe.tsx new file mode 100644 index 0000000000..900ceb26ac --- /dev/null +++ b/src/view/com/util/moderation/LabelsOnMe.tsx @@ -0,0 +1,86 @@ +import React from 'react' +import {StyleProp, View, ViewStyle} from 'react-native' +import {AppBskyFeedDefs, ComAtprotoLabelDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useSession} from '#/state/session' + +import {atoms as a} from '#/alf' +import {Button, ButtonText, ButtonIcon, ButtonSize} from '#/components/Button' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import {useOpenGlobalDialog} from '#/components/dialogs' +import {LabelsOnMeDialog} from '#/components/dialogs/LabelsOnMe' + +export function LabelsOnMe({ + details, + labels, + size, + style, +}: { + details: {did: string} | {uri: string; cid: string} + labels: ComAtprotoLabelDefs.Label[] | undefined + size?: ButtonSize + style?: StyleProp +}) { + const {_} = useLingui() + const {currentAccount} = useSession() + const isAccount = 'did' in details + const openDialog = useOpenGlobalDialog() + + if (!labels || !currentAccount) { + return null + } + labels = labels.filter( + l => !l.val.startsWith('!') && l.src !== currentAccount.did, + ) + if (!labels.length) { + return null + } + + return ( + + + + ) +} + +export function LabelsOnMyPost({ + post, + style, +}: { + post: AppBskyFeedDefs.PostView + style?: StyleProp +}) { + const {currentAccount} = useSession() + if (post.author.did !== currentAccount?.did) { + return null + } + return ( + + ) +} diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index cc12467cfe..a60908bc91 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -41,7 +41,6 @@ let PostCtrls = ({ post, record, richText, - showAppealLabelItem, style, onPressReply, }: { @@ -49,7 +48,6 @@ let PostCtrls = ({ post: Shadow record: AppBskyFeedPost.Record richText: RichTextAPI - showAppealLabelItem?: boolean style?: StyleProp onPressReply: () => void }): React.ReactNode => { @@ -229,7 +227,6 @@ let PostCtrls = ({ postUri={post.uri} record={record} richText={richText} - showAppealLabelItem={showAppealLabelItem} style={styles.btnPad} /> diff --git a/src/view/screens/DebugMod.tsx b/src/view/screens/DebugMod.tsx index 50f6ce8d66..53e39c4e50 100644 --- a/src/view/screens/DebugMod.tsx +++ b/src/view/screens/DebugMod.tsx @@ -17,6 +17,7 @@ import { RichText, } from '@atproto/api' import {moderationOptsOverrideContext} from '#/state/queries/preferences' +import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import {CenteredView, ScrollView} from '#/view/com/util/Views' @@ -41,7 +42,7 @@ const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys( ) as (keyof typeof LABELS)[] const MOCK_MOD_OPTS = { - userDid: 'did:web:alice.test', + userDid: '', adultContentEnabled: true, labelGroups: {}, mods: [ @@ -63,6 +64,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< const [target, setTarget] = React.useState(['account']) const [visibility, setVisiblity] = React.useState(['hide']) const labelStrings = useLabelStrings() + const {currentAccount} = useSession() const isTargetMe = scenario[0] === 'label' && scenarioSwitches.includes('targetMe') @@ -73,6 +75,9 @@ export const DebugModScreen = ({}: NativeStackScreenProps< const isLoggedOut = scenario[0] === 'label' && scenarioSwitches.includes('loggedOut') + const did = + isTargetMe && currentAccount ? currentAccount.did : 'did:web:bob.test' + const profile = React.useMemo(() => { const mockedProfile = mock.profileViewBasic({ handle: `bob.test`, @@ -82,17 +87,17 @@ export const DebugModScreen = ({}: NativeStackScreenProps< scenario[0] === 'label' && target[0] === 'account' ? [ mock.label({ - src: isSelfLabel ? 'did:web:bob.test' : undefined, + src: isSelfLabel ? did : undefined, val: label[0], - uri: `at://did:web:bob.test/`, + uri: `at://${did}/`, }), ] : scenario[0] === 'label' && target[0] === 'profile' ? [ mock.label({ - src: isSelfLabel ? 'did:web:bob.test' : undefined, + src: isSelfLabel ? did : undefined, val: label[0], - uri: `at://did:web:bob.test/app.bsky.actor.profile/self`, + uri: `at://${did}/app.bsky.actor.profile/self`, }), ] : undefined, @@ -102,16 +107,17 @@ export const DebugModScreen = ({}: NativeStackScreenProps< blockedBy: undefined, blocking: scenario[0] === 'block' - ? 'did://did:web:alice/app.bsky.actor.block/fake' + ? `at://did:web:alice.test/app.bsky.actor.block/fake` : undefined, blockingByList: undefined, }), }) + mockedProfile.did = did mockedProfile.avatar = 'https://bsky.social/about/images/favicon-32x32.png' mockedProfile.banner = 'https://bsky.social/about/images/social-card-default-gradient.png' return mockedProfile - }, [scenario, target, label, isSelfLabel]) + }, [scenario, target, label, isSelfLabel, did]) const post = React.useMemo(() => { return mock.postView({ @@ -123,9 +129,9 @@ export const DebugModScreen = ({}: NativeStackScreenProps< scenario[0] === 'label' && target[0] === 'post' ? [ mock.label({ - src: isSelfLabel ? 'did:web:bob.test' : undefined, + src: isSelfLabel ? did : undefined, val: label[0], - uri: `at://bob.test/app.bsky.feed.post/fake`, + uri: `at:/${did}/app.bsky.feed.post/fake`, }), ] : undefined, @@ -139,9 +145,9 @@ export const DebugModScreen = ({}: NativeStackScreenProps< scenario[0] === 'label' && target[0] === 'embed' ? [ mock.label({ - src: isSelfLabel ? 'did:web:bob.test' : undefined, + src: isSelfLabel ? did : undefined, val: label[0], - uri: `at://bob.test/app.bsky.feed.post/fake`, + uri: `at://${did}/app.bsky.feed.post/fake`, }), ] : undefined, @@ -160,23 +166,19 @@ export const DebugModScreen = ({}: NativeStackScreenProps< ], }, }) - }, [scenario, label, target, profile, isSelfLabel]) + }, [scenario, label, target, profile, isSelfLabel, did]) const modOpts = React.useMemo(() => { return { ...MOCK_MOD_OPTS, - userDid: isLoggedOut - ? '' - : isTargetMe - ? 'did:web:bob.test' - : 'did:web:alice.test', + userDid: isLoggedOut ? '' : isTargetMe ? did : 'did:web:alice.test', adultContentEnabled: !noAdult, labelGroups: { [LABELS[label[0] as keyof typeof LABELS].groupId]: visibility[0] as LabelPreference, }, } - }, [label, visibility, noAdult, isLoggedOut, isTargetMe]) + }, [label, visibility, noAdult, isLoggedOut, isTargetMe, did]) const profileModeration = React.useMemo(() => { return moderateProfile(profile, modOpts)