Collapse label appeal flow into ModerationDetailsDialog
Labels on your own posts previously rendered twice: once as normal label chips and again as an ominous "1 content label" pill whose only purpose was reaching the appeal dialog. Move the Appeal button into ModerationDetailsDialog (shown only when the label targets your own account or content and was not self-applied), extract AppealForm into its own module, and remove the redundant per-post pill. The account-level pill on the profile header is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,162 @@
|
|||||||
|
import {useState} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {type ComAtprotoLabelDefs, ToolsOzoneReportDefs} from '@atproto/api'
|
||||||
|
import {XRPCError} from '@atproto/api'
|
||||||
|
import {msg} from '@lingui/core/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
import {useMutation} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {useLabelSubject} from '#/lib/moderation'
|
||||||
|
import {useLabelInfo} from '#/lib/moderation/useLabelInfo'
|
||||||
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {useAgent} from '#/state/session'
|
||||||
|
import {atoms as a, useBreakpoints} from '#/alf'
|
||||||
|
import {Admonition} from '#/components/Admonition'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {InlineLinkText} from '#/components/Link'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
|
import * as Toast from '#/components/Toast'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {IS_ANDROID} from '#/env'
|
||||||
|
|
||||||
|
export function AppealForm({
|
||||||
|
label,
|
||||||
|
control,
|
||||||
|
onPressBack,
|
||||||
|
}: {
|
||||||
|
label: ComAtprotoLabelDefs.Label
|
||||||
|
control: Dialog.DialogOuterProps['control']
|
||||||
|
onPressBack: () => void
|
||||||
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {labeler, strings} = useLabelInfo(label)
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
const [details, setDetails] = useState('')
|
||||||
|
const {subject} = useLabelSubject({label})
|
||||||
|
const isAccountReport = 'did' in subject
|
||||||
|
const agent = useAgent()
|
||||||
|
const sourceName = labeler
|
||||||
|
? sanitizeHandle(labeler.creator.handle, '@')
|
||||||
|
: label.src
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const {mutate, isPending} = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
const $type = !isAccountReport
|
||||||
|
? 'com.atproto.repo.strongRef'
|
||||||
|
: 'com.atproto.admin.defs#repoRef'
|
||||||
|
await agent.createModerationReport(
|
||||||
|
{
|
||||||
|
reasonType: ToolsOzoneReportDefs.REASONAPPEAL,
|
||||||
|
subject: {
|
||||||
|
$type,
|
||||||
|
...subject,
|
||||||
|
},
|
||||||
|
reason: details,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
encoding: 'application/json',
|
||||||
|
headers: {
|
||||||
|
'atproto-proxy': `${label.src}#atproto_labeler`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onError: err => {
|
||||||
|
if (err instanceof XRPCError && err.error === 'AlreadyAppealed') {
|
||||||
|
setError(
|
||||||
|
_(
|
||||||
|
msg`You've already appealed this label and it's being reviewed by our moderation team.`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setError(_(msg`Failed to submit appeal, please try again.`))
|
||||||
|
}
|
||||||
|
logger.error('Failed to submit label appeal', {message: err})
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
control.close()
|
||||||
|
Toast.show(_(msg({message: 'Appeal submitted', context: 'toast'})))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit = () => mutate()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View>
|
||||||
|
<Text style={[a.text_2xl, a.font_semi_bold, a.pb_xs, a.leading_tight]}>
|
||||||
|
<Trans>Appeal "{strings.name}" label</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.text_md, a.leading_snug]}>
|
||||||
|
<Trans>
|
||||||
|
This appeal will be sent to{' '}
|
||||||
|
<InlineLinkText
|
||||||
|
label={sourceName}
|
||||||
|
to={makeProfileLink(
|
||||||
|
labeler ? labeler.creator : {did: label.src, handle: ''},
|
||||||
|
)}
|
||||||
|
onPress={() => control.close()}
|
||||||
|
style={[a.text_md, a.leading_snug]}>
|
||||||
|
{sourceName}
|
||||||
|
</InlineLinkText>
|
||||||
|
.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
{error && (
|
||||||
|
<Admonition type="error" style={[a.mt_sm]}>
|
||||||
|
{error}
|
||||||
|
</Admonition>
|
||||||
|
)}
|
||||||
|
<View style={[a.my_md]}>
|
||||||
|
<Dialog.Input
|
||||||
|
label={_(msg`Text input field`)}
|
||||||
|
placeholder={_(
|
||||||
|
msg`Please explain why you think this label was incorrectly applied by ${
|
||||||
|
labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src
|
||||||
|
}`,
|
||||||
|
)}
|
||||||
|
value={details}
|
||||||
|
onChangeText={setDetails}
|
||||||
|
autoFocus={true}
|
||||||
|
numberOfLines={3}
|
||||||
|
multiline
|
||||||
|
maxLength={300}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={
|
||||||
|
gtMobile
|
||||||
|
? [a.flex_row, a.justify_between]
|
||||||
|
: [{flexDirection: 'column-reverse'}, a.gap_sm]
|
||||||
|
}>
|
||||||
|
<Button
|
||||||
|
testID="backBtn"
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
onPress={onPressBack}
|
||||||
|
label={_(msg`Back`)}>
|
||||||
|
<ButtonText>{_(msg`Back`)}</ButtonText>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
testID="submitBtn"
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
onPress={onSubmit}
|
||||||
|
label={_(msg`Submit`)}>
|
||||||
|
<ButtonText>{_(msg`Submit`)}</ButtonText>
|
||||||
|
{isPending && <ButtonIcon icon={Loader} />}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
{IS_ANDROID && <View style={{height: 300}} />}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
||||||
import {type AppBskyFeedDefs, type ComAtprotoLabelDefs} from '@atproto/api'
|
import {type ComAtprotoLabelDefs} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Plural} from '@lingui/react/macro'
|
import {Plural} from '@lingui/react/macro'
|
||||||
@@ -19,12 +19,10 @@ import {
|
|||||||
} from '#/components/moderation/LabelsOnMeDialog'
|
} from '#/components/moderation/LabelsOnMeDialog'
|
||||||
|
|
||||||
export function LabelsOnMe({
|
export function LabelsOnMe({
|
||||||
type,
|
|
||||||
labels,
|
labels,
|
||||||
size,
|
size,
|
||||||
style,
|
style,
|
||||||
}: {
|
}: {
|
||||||
type: 'account' | 'content'
|
|
||||||
labels: ComAtprotoLabelDefs.Label[] | undefined
|
labels: ComAtprotoLabelDefs.Label[] | undefined
|
||||||
size?: ButtonSize
|
size?: ButtonSize
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
@@ -47,7 +45,7 @@ export function LabelsOnMe({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.flex_row, style]}>
|
<View style={[a.flex_row, style]}>
|
||||||
<LabelsOnMeDialog control={control} labels={labels} type={type} />
|
<LabelsOnMeDialog control={control} labels={labels} type="account" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
@@ -59,37 +57,13 @@ export function LabelsOnMe({
|
|||||||
}}>
|
}}>
|
||||||
<ButtonIcon position="left" icon={CircleInfo} />
|
<ButtonIcon position="left" icon={CircleInfo} />
|
||||||
<ButtonText style={[a.leading_snug]}>
|
<ButtonText style={[a.leading_snug]}>
|
||||||
{type === 'account' ? (
|
<Plural
|
||||||
<Plural
|
value={labels.length}
|
||||||
value={labels.length}
|
one="# account label"
|
||||||
one="# account label"
|
other="# account labels"
|
||||||
other="# account labels"
|
/>
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Plural
|
|
||||||
value={labels.length}
|
|
||||||
one="# content label"
|
|
||||||
other="# content labels"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LabelsOnMyPost({
|
|
||||||
post,
|
|
||||||
style,
|
|
||||||
}: {
|
|
||||||
post: AppBskyFeedDefs.PostView
|
|
||||||
style?: StyleProp<ViewStyle>
|
|
||||||
}) {
|
|
||||||
const {currentAccount} = useSession()
|
|
||||||
if (post.author.did !== currentAccount?.did) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<LabelsOnMe type="content" labels={post.labels} size="tiny" style={style} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,29 +1,22 @@
|
|||||||
import {useCallback, useMemo, useState} from 'react'
|
import {useMemo, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {type ComAtprotoLabelDefs, ToolsOzoneReportDefs} from '@atproto/api'
|
import {type ComAtprotoLabelDefs} from '@atproto/api'
|
||||||
import {XRPCError} from '@atproto/api'
|
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
import {useMutation} from '@tanstack/react-query'
|
|
||||||
|
|
||||||
import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
|
import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
|
||||||
import {useLabelSubject} from '#/lib/moderation'
|
|
||||||
import {useLabelInfo} from '#/lib/moderation/useLabelInfo'
|
import {useLabelInfo} from '#/lib/moderation/useLabelInfo'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {logger} from '#/logger'
|
import {useSession} from '#/state/session'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {InlineLinkText} from '#/components/Link'
|
import {InlineLinkText} from '#/components/Link'
|
||||||
import * as Toast from '#/components/Toast'
|
import {AppealForm} from '#/components/moderation/AppealForm'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {IS_ANDROID} from '#/env'
|
|
||||||
import {Admonition} from '../Admonition'
|
|
||||||
import {Divider} from '../Divider'
|
import {Divider} from '../Divider'
|
||||||
import {Loader} from '../Loader'
|
|
||||||
|
|
||||||
export {useDialogControl as useLabelsOnMeDialogControl} from '#/components/Dialog'
|
export {useDialogControl as useLabelsOnMeDialogControl} from '#/components/Dialog'
|
||||||
|
|
||||||
@@ -211,141 +204,3 @@ function Label({
|
|||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function AppealForm({
|
|
||||||
label,
|
|
||||||
control,
|
|
||||||
onPressBack,
|
|
||||||
}: {
|
|
||||||
label: ComAtprotoLabelDefs.Label
|
|
||||||
control: Dialog.DialogOuterProps['control']
|
|
||||||
onPressBack: () => void
|
|
||||||
}) {
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {labeler, strings} = useLabelInfo(label)
|
|
||||||
const {gtMobile} = useBreakpoints()
|
|
||||||
const [details, setDetails] = useState('')
|
|
||||||
const {subject} = useLabelSubject({label})
|
|
||||||
const isAccountReport = 'did' in subject
|
|
||||||
const agent = useAgent()
|
|
||||||
const sourceName = labeler
|
|
||||||
? sanitizeHandle(labeler.creator.handle, '@')
|
|
||||||
: label.src
|
|
||||||
const [error, setError] = useState<string | null>(null)
|
|
||||||
|
|
||||||
const {mutate, isPending} = useMutation({
|
|
||||||
mutationFn: async () => {
|
|
||||||
const $type = !isAccountReport
|
|
||||||
? 'com.atproto.repo.strongRef'
|
|
||||||
: 'com.atproto.admin.defs#repoRef'
|
|
||||||
await agent.createModerationReport(
|
|
||||||
{
|
|
||||||
reasonType: ToolsOzoneReportDefs.REASONAPPEAL,
|
|
||||||
subject: {
|
|
||||||
$type,
|
|
||||||
...subject,
|
|
||||||
},
|
|
||||||
reason: details,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
encoding: 'application/json',
|
|
||||||
headers: {
|
|
||||||
'atproto-proxy': `${label.src}#atproto_labeler`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onError: err => {
|
|
||||||
if (err instanceof XRPCError && err.error === 'AlreadyAppealed') {
|
|
||||||
setError(
|
|
||||||
_(
|
|
||||||
msg`You've already appealed this label and it's being reviewed by our moderation team.`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
setError(_(msg`Failed to submit appeal, please try again.`))
|
|
||||||
}
|
|
||||||
logger.error('Failed to submit label appeal', {message: err})
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
control.close()
|
|
||||||
Toast.show(_(msg({message: 'Appeal submitted', context: 'toast'})))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSubmit = useCallback(() => mutate(), [mutate])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<View>
|
|
||||||
<Text style={[a.text_2xl, a.font_semi_bold, a.pb_xs, a.leading_tight]}>
|
|
||||||
<Trans>Appeal "{strings.name}" label</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text style={[a.text_md, a.leading_snug]}>
|
|
||||||
<Trans>
|
|
||||||
This appeal will be sent to{' '}
|
|
||||||
<InlineLinkText
|
|
||||||
label={sourceName}
|
|
||||||
to={makeProfileLink(
|
|
||||||
labeler ? labeler.creator : {did: label.src, handle: ''},
|
|
||||||
)}
|
|
||||||
onPress={() => control.close()}
|
|
||||||
style={[a.text_md, a.leading_snug]}>
|
|
||||||
{sourceName}
|
|
||||||
</InlineLinkText>
|
|
||||||
.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
{error && (
|
|
||||||
<Admonition type="error" style={[a.mt_sm]}>
|
|
||||||
{error}
|
|
||||||
</Admonition>
|
|
||||||
)}
|
|
||||||
<View style={[a.my_md]}>
|
|
||||||
<Dialog.Input
|
|
||||||
label={_(msg`Text input field`)}
|
|
||||||
placeholder={_(
|
|
||||||
msg`Please explain why you think this label was incorrectly applied by ${
|
|
||||||
labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src
|
|
||||||
}`,
|
|
||||||
)}
|
|
||||||
value={details}
|
|
||||||
onChangeText={setDetails}
|
|
||||||
autoFocus={true}
|
|
||||||
numberOfLines={3}
|
|
||||||
multiline
|
|
||||||
maxLength={300}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={
|
|
||||||
gtMobile
|
|
||||||
? [a.flex_row, a.justify_between]
|
|
||||||
: [{flexDirection: 'column-reverse'}, a.gap_sm]
|
|
||||||
}>
|
|
||||||
<Button
|
|
||||||
testID="backBtn"
|
|
||||||
variant="solid"
|
|
||||||
color="secondary"
|
|
||||||
size="large"
|
|
||||||
onPress={onPressBack}
|
|
||||||
label={_(msg`Back`)}>
|
|
||||||
<ButtonText>{_(msg`Back`)}</ButtonText>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
testID="submitBtn"
|
|
||||||
variant="solid"
|
|
||||||
color="primary"
|
|
||||||
size="large"
|
|
||||||
onPress={onSubmit}
|
|
||||||
label={_(msg`Submit`)}>
|
|
||||||
<ButtonText>{_(msg`Submit`)}</ButtonText>
|
|
||||||
{isPending && <ButtonIcon icon={Loader} />}
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
{IS_ANDROID && <View style={{height: 300}} />}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {type ModerationCause} from '@atproto/api'
|
import {type ModerationCause} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
@@ -11,8 +12,10 @@ import {listUriToHref} from '#/lib/strings/url-helpers'
|
|||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {atoms as a, useGutters, useTheme, web} from '#/alf'
|
import {atoms as a, useGutters, useTheme, web} from '#/alf'
|
||||||
import {Admonition} from '#/components/Admonition'
|
import {Admonition} from '#/components/Admonition'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {InlineLinkText} from '#/components/Link'
|
import {InlineLinkText} from '#/components/Link'
|
||||||
|
import {AppealForm} from '#/components/moderation/AppealForm'
|
||||||
import {type AppModerationCause} from '#/components/Pills'
|
import {type AppModerationCause} from '#/components/Pills'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {IS_NATIVE} from '#/env'
|
import {IS_NATIVE} from '#/env'
|
||||||
@@ -47,6 +50,18 @@ function ModerationDetailsDialogInner({
|
|||||||
const desc = useModerationCauseDescription(modcause)
|
const desc = useModerationCauseDescription(modcause)
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const timeDiff = useGetTimeAgo({future: true})
|
const timeDiff = useGetTimeAgo({future: true})
|
||||||
|
const [isAppealing, setIsAppealing] = useState(false)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Appeal eligibility: only for label causes on content belonging to the
|
||||||
|
* current user, where the label was not self-applied.
|
||||||
|
*/
|
||||||
|
const canAppeal =
|
||||||
|
modcause?.type === 'label' &&
|
||||||
|
!!currentAccount &&
|
||||||
|
modcause.label.src !== currentAccount.did &&
|
||||||
|
(modcause.label.uri === currentAccount.did ||
|
||||||
|
modcause.label.uri.startsWith(`at://${currentAccount.did}/`))
|
||||||
|
|
||||||
let name
|
let name
|
||||||
let description
|
let description
|
||||||
@@ -137,6 +152,23 @@ function ModerationDetailsDialogInner({
|
|||||||
const sourceName =
|
const sourceName =
|
||||||
desc.source || desc.sourceDisplayName || _(msg`an unknown labeler`)
|
desc.source || desc.sourceDisplayName || _(msg`an unknown labeler`)
|
||||||
|
|
||||||
|
if (isAppealing && modcause?.type === 'label') {
|
||||||
|
return (
|
||||||
|
<Dialog.ScrollableInner
|
||||||
|
label={_(msg`Appeal label`)}
|
||||||
|
style={web({
|
||||||
|
maxWidth: 460,
|
||||||
|
})}>
|
||||||
|
<AppealForm
|
||||||
|
label={modcause.label}
|
||||||
|
control={control}
|
||||||
|
onPressBack={() => setIsAppealing(false)}
|
||||||
|
/>
|
||||||
|
<Dialog.Close />
|
||||||
|
</Dialog.ScrollableInner>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.ScrollableInner
|
<Dialog.ScrollableInner
|
||||||
label={_(msg`Moderation details`)}
|
label={_(msg`Moderation details`)}
|
||||||
@@ -228,6 +260,20 @@ function ModerationDetailsDialogInner({
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
{canAppeal && (
|
||||||
|
<View style={[a.flex_row, a.justify_end, a.mt_sm]}>
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
label={_(msg`Appeal this label`)}
|
||||||
|
onPress={() => setIsAppealing(true)}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Appeal</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Tra
|
|||||||
import {GalleryBleed} from '#/components/images/Gallery'
|
import {GalleryBleed} from '#/components/images/Gallery'
|
||||||
import {Link} from '#/components/Link'
|
import {Link} from '#/components/Link'
|
||||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||||
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
|
|
||||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||||
import {type AppModerationCause} from '#/components/Pills'
|
import {type AppModerationCause} from '#/components/Pills'
|
||||||
import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
|
import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
|
||||||
@@ -384,7 +383,6 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={[a.pb_sm]}>
|
<View style={[a.pb_sm]}>
|
||||||
<LabelsOnMyPost post={post} style={[a.pb_sm]} />
|
|
||||||
<ContentHider
|
<ContentHider
|
||||||
modui={moderation.ui('contentView')}
|
modui={moderation.ui('contentView')}
|
||||||
ignoreMute
|
ignoreMute
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import {
|
|||||||
GalleryBleed,
|
GalleryBleed,
|
||||||
maybeApplyGalleryOffsetStyles,
|
maybeApplyGalleryOffsetStyles,
|
||||||
} from '#/components/images/Gallery'
|
} from '#/components/images/Gallery'
|
||||||
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
|
|
||||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||||
import {PostHider} from '#/components/moderation/PostHider'
|
import {PostHider} from '#/components/moderation/PostHider'
|
||||||
import {type AppModerationCause} from '#/components/Pills'
|
import {type AppModerationCause} from '#/components/Pills'
|
||||||
@@ -310,7 +309,6 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
|
|||||||
}),
|
}),
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<LabelsOnMyPost post={post} style={[a.pb_xs]} />
|
|
||||||
<PostAlerts
|
<PostAlerts
|
||||||
modui={moderation.ui('contentList')}
|
modui={moderation.ui('contentList')}
|
||||||
style={[a.pb_2xs]}
|
style={[a.pb_2xs]}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import {DebugFieldDisplay} from '#/components/DebugFieldDisplay'
|
|||||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
|
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
|
||||||
import {GalleryBleed} from '#/components/images/Gallery'
|
import {GalleryBleed} from '#/components/images/Gallery'
|
||||||
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
|
|
||||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||||
import {PostHider} from '#/components/moderation/PostHider'
|
import {PostHider} from '#/components/moderation/PostHider'
|
||||||
import {type AppModerationCause} from '#/components/Pills'
|
import {type AppModerationCause} from '#/components/Pills'
|
||||||
@@ -340,7 +339,6 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
|
|||||||
<View style={[a.flex_row]}>
|
<View style={[a.flex_row]}>
|
||||||
<ThreadItemTreeReplyChildReplyLine item={item} />
|
<ThreadItemTreeReplyChildReplyLine item={item} />
|
||||||
<View style={[a.flex_1, a.pl_2xs]}>
|
<View style={[a.flex_1, a.pl_2xs]}>
|
||||||
<LabelsOnMyPost post={post} style={[a.pb_2xs]} />
|
|
||||||
<PostAlerts
|
<PostAlerts
|
||||||
modui={moderation.ui('contentList')}
|
modui={moderation.ui('contentList')}
|
||||||
style={[a.pb_2xs]}
|
style={[a.pb_2xs]}
|
||||||
|
|||||||
@@ -228,7 +228,6 @@ let ProfileHeaderShell = ({
|
|||||||
{!isPlaceholderProfile &&
|
{!isPlaceholderProfile &&
|
||||||
(isMe ? (
|
(isMe ? (
|
||||||
<LabelsOnMe
|
<LabelsOnMe
|
||||||
type="account"
|
|
||||||
labels={profile.labels}
|
labels={profile.labels}
|
||||||
style={[
|
style={[
|
||||||
a.px_lg,
|
a.px_lg,
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import {
|
|||||||
maybeApplyGalleryOffsetStyles,
|
maybeApplyGalleryOffsetStyles,
|
||||||
} from '#/components/images/Gallery'
|
} from '#/components/images/Gallery'
|
||||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||||
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
|
|
||||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||||
import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
|
import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
|
||||||
import {PostRepliedTo} from '#/components/Post/PostRepliedTo'
|
import {PostRepliedTo} from '#/components/Post/PostRepliedTo'
|
||||||
@@ -215,7 +214,6 @@ function PostInner({
|
|||||||
{replyAuthorDid !== '' && (
|
{replyAuthorDid !== '' && (
|
||||||
<PostRepliedTo parentAuthor={replyAuthorDid} />
|
<PostRepliedTo parentAuthor={replyAuthorDid} />
|
||||||
)}
|
)}
|
||||||
<LabelsOnMyPost post={post} />
|
|
||||||
<ContentHider
|
<ContentHider
|
||||||
modui={moderation.ui('contentView')}
|
modui={moderation.ui('contentView')}
|
||||||
style={styles.contentHider}
|
style={styles.contentHider}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import {
|
|||||||
maybeApplyGalleryOffsetStyles,
|
maybeApplyGalleryOffsetStyles,
|
||||||
} from '#/components/images/Gallery'
|
} from '#/components/images/Gallery'
|
||||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||||
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
|
|
||||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||||
import {type AppModerationCause} from '#/components/Pills'
|
import {type AppModerationCause} from '#/components/Pills'
|
||||||
import {Embed} from '#/components/Post/Embed'
|
import {Embed} from '#/components/Post/Embed'
|
||||||
@@ -420,7 +419,6 @@ let FeedItemInner = ({
|
|||||||
isParentNotFound={isParentNotFound}
|
isParentNotFound={isParentNotFound}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<LabelsOnMyPost post={post} />
|
|
||||||
<PostContent
|
<PostContent
|
||||||
moderation={moderation}
|
moderation={moderation}
|
||||||
richText={richText}
|
richText={richText}
|
||||||
|
|||||||
Reference in New Issue
Block a user