Collapse label appeal flow into ModerationDetailsDialog (#11091)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
Austin McKinley
2026-07-10 16:26:10 -07:00
committed by GitHub
parent fa27d446d8
commit b4dfa3cc22
16 changed files with 511 additions and 265 deletions
+126 -8
View File
@@ -1,28 +1,92 @@
import {type StyleProp, type ViewStyle} from 'react-native'
import {type ModerationCause, type ModerationUI} from '@atproto/api'
import {type StyleProp, View, type ViewStyle} from 'react-native'
import {
type AppBskyFeedDefs,
type ComAtprotoLabelDefs,
type ModerationCause,
type ModerationUI,
} from '@atproto/api'
import {plural} from '@lingui/core/macro'
import {useLingui} from '@lingui/react/macro'
import {getModerationCauseKey, unique} from '#/lib/moderation'
import {
filterUserFacingLabels,
getModerationCauseKey,
unique,
} from '#/lib/moderation'
import {useSession} from '#/state/session'
import {atoms as a} from '#/alf'
import {
LabelsOnMeDialog,
useLabelsOnMeDialogControl,
} from '#/components/moderation/LabelsOnMeDialog'
import * as Pills from '#/components/Pills'
export function PostAlerts({
post,
modui,
size = 'sm',
view = 'compact',
style,
additionalCauses,
}: {
post?: AppBskyFeedDefs.PostView
modui: ModerationUI
size?: Pills.CommonProps['size']
/**
* Expanded views (e.g. the thread anchor post) render larger pills and
* surface the "+n" additional labels pill. Compact views (feeds, replies)
* keep the alerts minimal.
*/
view?: 'expanded' | 'compact'
includeMute?: boolean
style?: StyleProp<ViewStyle>
additionalCauses?: ModerationCause[] | Pills.AppModerationCause[]
}) {
if (!modui.alert && !modui.inform && !additionalCauses?.length) {
const {currentAccount} = useSession()
const size: Pills.CommonProps['size'] = view === 'expanded' ? 'lg' : 'sm'
const alerts = modui.alerts.filter(unique)
const informs = modui.informs.filter(unique)
/*
* The "+n" pill surfaces labels for the author to review and appeal, so it
* only applies when the viewer is the author, and only in expanded views.
* It renders even when no other moderation is visible, since it may be the
* author's only entry point to appeal labels on their content.
*/
const isOwnPost = !!post && post.author.did === currentAccount?.did
const allLabels: ComAtprotoLabelDefs.Label[] =
isOwnPost && view === 'expanded'
? [...(post.labels ?? []), ...(post.author.labels ?? [])]
: []
/*
* Labels that the moderation system already surfaces in this context -
* whether as an alert, an inform, or a blur handled by ContentHider - should
* not be repeated in the "+n" pill.
*/
const shownCauses = [...alerts, ...informs, ...modui.blurs]
const additionalLabels = filterUserFacingLabels(
allLabels,
currentAccount?.did,
).filter(label =>
shownCauses.every(
cause =>
cause.type !== 'label' ||
cause.label.val !== label.val ||
cause.label.src !== label.src ||
cause.label.uri !== label.uri,
),
)
if (
!modui.alert &&
!modui.inform &&
!additionalCauses?.length &&
!additionalLabels.length
) {
return null
}
return (
<Pills.Row size={size} style={[size === 'sm' && {marginLeft: -3}, style]}>
{modui.alerts.filter(unique).map(cause => (
{alerts.map(cause => (
<Pills.Label
key={getModerationCauseKey(cause)}
cause={cause}
@@ -30,7 +94,7 @@ export function PostAlerts({
noBg={size === 'sm'}
/>
))}
{modui.informs.filter(unique).map(cause => (
{informs.map(cause => (
<Pills.Label
key={getModerationCauseKey(cause)}
cause={cause}
@@ -46,6 +110,60 @@ export function PostAlerts({
noBg={size === 'sm'}
/>
))}
{additionalLabels.length ? (
<AdditionalLabels
labels={additionalLabels}
size={size}
hasPrecedingPills={
alerts.length > 0 ||
informs.length > 0 ||
(additionalCauses?.length ?? 0) > 0
}
/>
) : null}
</Pills.Row>
)
}
function AdditionalLabels({
labels,
size,
hasPrecedingPills,
}: {
labels: ComAtprotoLabelDefs.Label[]
size?: Pills.CommonProps['size']
/**
* The compact "+n" syntax only makes sense as a continuation of other
* pills. When this pill stands alone, spell it out.
*/
hasPrecedingPills: boolean
}) {
const {t: l} = useLingui()
const control = useLabelsOnMeDialogControl()
return (
<View style={[a.flex_row]}>
<LabelsOnMeDialog control={control} labels={labels} type="content" />
<Pills.LabelBase
label={l`${plural(labels.length, {
one: '# label applied to your post',
other: '# labels applied to your post',
})}`}
cta={
hasPrecedingPills
? l`+${labels.length}`
: l`${plural(labels.length, {
one: '# label applied',
other: '# labels applied',
})}`
}
size={size}
noBg={size === 'sm'}
onPress={() => {
control.open()
}}
/>
</View>
)
}