Get report flow working again with temporarily fixed report options
This commit is contained in:
@@ -0,0 +1,178 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {AppBskyLabelerDefs} from '@atproto/api'
|
||||||
|
|
||||||
|
import {useReportOptions, ReportOption} from '#/lib/moderation/useReportOptions'
|
||||||
|
import {DMCA_LINK} from '#/components/ReportDialog/const'
|
||||||
|
import {Link} from '#/components/Link'
|
||||||
|
export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
|
||||||
|
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonIcon,
|
||||||
|
ButtonText,
|
||||||
|
useButtonContext,
|
||||||
|
} from '#/components/Button'
|
||||||
|
import {Divider} from '#/components/Divider'
|
||||||
|
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
|
||||||
|
import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRight} from '#/components/icons/SquareArrowTopRight'
|
||||||
|
|
||||||
|
import {ReportDialogProps} from './types'
|
||||||
|
|
||||||
|
export function SelectReportOptionView({
|
||||||
|
...props
|
||||||
|
}: ReportDialogProps & {
|
||||||
|
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
|
||||||
|
onSelectReportOption: (reportOption: ReportOption) => void
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
const allReportOptions = useReportOptions()
|
||||||
|
const reportOptions = allReportOptions[props.params.type]
|
||||||
|
|
||||||
|
const i18n = React.useMemo(() => {
|
||||||
|
let title = _(msg`Report this content`)
|
||||||
|
let description = _(msg`Why should this content be reviewed?`)
|
||||||
|
|
||||||
|
if (props.params.type === 'account') {
|
||||||
|
title = _(msg`Report this user`)
|
||||||
|
description = _(msg`Why should this user be reviewed?`)
|
||||||
|
} else if (props.params.type === 'post') {
|
||||||
|
title = _(msg`Report this post`)
|
||||||
|
description = _(msg`Why should this post be reviewed?`)
|
||||||
|
} else if (props.params.type === 'list') {
|
||||||
|
title = _(msg`Report this list`)
|
||||||
|
description = _(msg`Why should this list be reviewed?`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
}
|
||||||
|
}, [_, props.params.type])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[a.gap_lg]}>
|
||||||
|
<View style={[a.justify_center, a.gap_sm]}>
|
||||||
|
<Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
|
||||||
|
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||||
|
{i18n.description}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<View style={[a.gap_sm, {marginHorizontal: a.p_md.padding * -1}]}>
|
||||||
|
{reportOptions.map(reportOption => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={reportOption.reason}
|
||||||
|
label={_(msg`Create report for ${reportOption.title}`)}
|
||||||
|
onPress={() => props.onSelectReportOption(reportOption)}>
|
||||||
|
<ReportOptionButton
|
||||||
|
title={reportOption.title}
|
||||||
|
description={reportOption.description}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
{(props.params.type === 'post' || props.params.type === 'account') && (
|
||||||
|
<View style={[a.pt_md, a.px_md]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_between,
|
||||||
|
a.gap_lg,
|
||||||
|
a.p_md,
|
||||||
|
a.pl_lg,
|
||||||
|
a.rounded_md,
|
||||||
|
t.atoms.bg_contrast_900,
|
||||||
|
]}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
t.atoms.text_inverted,
|
||||||
|
a.italic,
|
||||||
|
a.leading_snug,
|
||||||
|
]}>
|
||||||
|
<Trans>Need to report a copyright violation?</Trans>
|
||||||
|
</Text>
|
||||||
|
<Link
|
||||||
|
to={DMCA_LINK}
|
||||||
|
label={_(msg`View details for reporting a copyright violation`)}
|
||||||
|
size="small"
|
||||||
|
variant="solid"
|
||||||
|
color="secondary">
|
||||||
|
<ButtonText>View details</ButtonText>
|
||||||
|
<ButtonIcon position="right" icon={SquareArrowTopRight} />
|
||||||
|
</Link>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReportOptionButton({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {hovered, focused, pressed} = useButtonContext()
|
||||||
|
const interacted = hovered || focused || pressed
|
||||||
|
|
||||||
|
const styles = React.useMemo(() => {
|
||||||
|
return {
|
||||||
|
interacted: {
|
||||||
|
backgroundColor: t.palette.contrast_50,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}, [t])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_between,
|
||||||
|
a.p_md,
|
||||||
|
a.rounded_md,
|
||||||
|
{paddingRight: 70},
|
||||||
|
interacted && styles.interacted,
|
||||||
|
]}>
|
||||||
|
<View style={[a.flex_1, a.gap_xs]}>
|
||||||
|
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.leading_tight, {maxWidth: 400}]}>{description}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
a.justify_center,
|
||||||
|
a.pr_md,
|
||||||
|
{left: 'auto'},
|
||||||
|
]}>
|
||||||
|
<ChevronRight
|
||||||
|
size="md"
|
||||||
|
fill={
|
||||||
|
hovered ? t.palette.primary_500 : t.atoms.text_contrast_low.color
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {AppBskyLabelerDefs} from '@atproto/api'
|
||||||
|
|
||||||
|
import {getModerationServiceTitle} from '#/lib/moderation'
|
||||||
|
import {ReportOption} from '#/lib/moderation/useReportOptions'
|
||||||
|
|
||||||
|
import {atoms as a, useTheme, tokens, native} from '#/alf'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
|
||||||
|
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||||
|
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
||||||
|
import {GradientFill} from '#/components/GradientFill'
|
||||||
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
|
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
|
|
||||||
|
import {ReportDialogProps} from './types'
|
||||||
|
|
||||||
|
export function SubmitView({
|
||||||
|
params,
|
||||||
|
labelers,
|
||||||
|
selectedReportOption,
|
||||||
|
goBack,
|
||||||
|
onSubmitComplete,
|
||||||
|
}: ReportDialogProps & {
|
||||||
|
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
|
||||||
|
selectedReportOption: ReportOption
|
||||||
|
goBack: () => void
|
||||||
|
onSubmitComplete: () => void
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
const [details, setDetails] = React.useState<string>('')
|
||||||
|
const [submitting, setSubmitting] = React.useState<boolean>(false)
|
||||||
|
const [selectedServices, setSelectedServices] = React.useState<string[]>(
|
||||||
|
labelers?.map(labeler => labeler.creator.did) || [],
|
||||||
|
)
|
||||||
|
|
||||||
|
const submit = React.useCallback(async () => {
|
||||||
|
setSubmitting(true)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
|
const $type =
|
||||||
|
params.type === 'account'
|
||||||
|
? 'com.atproto.admin.defs#repoRef'
|
||||||
|
: 'com.atproto.repo.strongRef'
|
||||||
|
const report = {
|
||||||
|
reasonType: selectedReportOption.reason,
|
||||||
|
subject: {
|
||||||
|
$type,
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
reason: details,
|
||||||
|
}
|
||||||
|
console.log(report)
|
||||||
|
// await getAgent().createModerationReport(report)
|
||||||
|
|
||||||
|
setSubmitting(false)
|
||||||
|
|
||||||
|
Toast.show(`Thank you. Your report has been sent.`)
|
||||||
|
|
||||||
|
onSubmitComplete()
|
||||||
|
}, [params, details, selectedReportOption, onSubmitComplete])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[a.gap_2xl]}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
shape="round"
|
||||||
|
label={_(msg`Go back to previous step`)}
|
||||||
|
onPress={goBack}>
|
||||||
|
<ButtonIcon icon={ChevronLeft} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_between,
|
||||||
|
a.gap_lg,
|
||||||
|
a.p_md,
|
||||||
|
a.rounded_md,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
]}>
|
||||||
|
<View style={[a.flex_1, a.gap_xs]}>
|
||||||
|
<Text style={[a.text_md, a.font_bold]}>
|
||||||
|
{selectedReportOption.title}
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.leading_tight, {maxWidth: 400}]}>
|
||||||
|
{selectedReportOption.description}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Check size="md" style={[a.pr_sm]} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[a.gap_md]}>
|
||||||
|
<Text style={[t.atoms.text_contrast_medium]}>
|
||||||
|
<Trans>Select the moderation service(s) to report to</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Toggle.Group
|
||||||
|
label="Select mod services"
|
||||||
|
values={selectedServices}
|
||||||
|
onChange={setSelectedServices}>
|
||||||
|
<View style={[a.flex_row, a.gap_sm, a.flex_wrap]}>
|
||||||
|
{labelers.map(labeler => {
|
||||||
|
const title = getModerationServiceTitle({
|
||||||
|
displayName: labeler.creator.displayName,
|
||||||
|
handle: labeler.creator.handle,
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<Toggle.Item
|
||||||
|
key={labeler.creator.did}
|
||||||
|
name={labeler.creator.did}
|
||||||
|
label={title}>
|
||||||
|
<LabelerToggle title={title} />
|
||||||
|
</Toggle.Item>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
</Toggle.Group>
|
||||||
|
</View>
|
||||||
|
<View style={[a.gap_md]}>
|
||||||
|
<Text style={[t.atoms.text_contrast_medium]}>
|
||||||
|
<Trans>Optionally provide additional information below:</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<View style={[a.relative, a.w_full]}>
|
||||||
|
<Dialog.Input
|
||||||
|
multiline
|
||||||
|
value={details}
|
||||||
|
onChangeText={setDetails}
|
||||||
|
label="Text field"
|
||||||
|
style={{paddingRight: 60}}
|
||||||
|
numberOfLines={6}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.pr_md,
|
||||||
|
a.pb_sm,
|
||||||
|
{
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<CharProgress count={details?.length || 0} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[a.flex_row, a.align_center, a.justify_end, a.gap_lg]}>
|
||||||
|
{!selectedServices.length && (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
a.italic,
|
||||||
|
a.leading_snug,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<Trans>You must select at least one labeler for a report</Trans>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
label={_(msg`Submit`)}
|
||||||
|
onPress={submit}
|
||||||
|
disabled={!selectedServices.length}>
|
||||||
|
<ButtonText>Submit</ButtonText>
|
||||||
|
{submitting && <ButtonIcon icon={Loader} />}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function LabelerToggle({title}: {title: string}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const ctx = Toggle.useItemContext()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.py_md,
|
||||||
|
a.px_xl,
|
||||||
|
a.rounded_full,
|
||||||
|
a.overflow_hidden,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
]}>
|
||||||
|
{ctx.selected && <GradientFill gradient={tokens.gradients.midnight} />}
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_between,
|
||||||
|
a.gap_lg,
|
||||||
|
a.z_10,
|
||||||
|
]}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
native({marginTop: 2}),
|
||||||
|
{
|
||||||
|
color:
|
||||||
|
t.name === 'light' && ctx.selected
|
||||||
|
? t.palette.white
|
||||||
|
: t.atoms.text.color,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<Plus
|
||||||
|
size="sm"
|
||||||
|
fill={
|
||||||
|
ctx.selected
|
||||||
|
? t.palette.primary_200
|
||||||
|
: t.atoms.text_contrast_low.color
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,498 +1,65 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View, Linking, Pressable} from 'react-native'
|
import {View, Linking, Pressable} from 'react-native'
|
||||||
import {msg} from '@lingui/macro'
|
import {AppBskyLabelerDefs} from '@atproto/api'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {AppBskyLabelerDefs, LabelGroupDefinition} from '@atproto/api'
|
|
||||||
|
|
||||||
import {atoms as a, useTheme, tokens, native} from '#/alf'
|
import {useMyLabelers} from '#/state/queries/preferences'
|
||||||
import {Text} from '#/components/Typography'
|
import {ReportOption} from '#/lib/moderation/useReportOptions'
|
||||||
import * as Dialog from '#/components/Dialog'
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
ButtonIcon,
|
|
||||||
ButtonText,
|
|
||||||
useButtonContext,
|
|
||||||
} from '#/components/Button'
|
|
||||||
import {Divider} from '#/components/Divider'
|
|
||||||
import {useLabelGroupStrings} from '#/lib/moderation/useLabelGroupStrings'
|
|
||||||
import {
|
|
||||||
ChevronRight_Stroke2_Corner0_Rounded as ChevronRight,
|
|
||||||
ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft,
|
|
||||||
} from '#/components/icons/Chevron'
|
|
||||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
|
||||||
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
|
||||||
import {GradientFill} from '#/components/GradientFill'
|
|
||||||
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
|
|
||||||
import {Loader} from '#/components/Loader'
|
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
|
||||||
import {useLabelersDetailedInfoQuery} from '#/state/queries/labeler'
|
|
||||||
import {
|
|
||||||
getModerationServiceTitle,
|
|
||||||
useConfigurableContentLabelGroups,
|
|
||||||
useConfigurableProfileLabelGroups,
|
|
||||||
} from '#/lib/moderation'
|
|
||||||
import {DMCA_LINK} from '#/components/ReportDialog/const'
|
import {DMCA_LINK} from '#/components/ReportDialog/const'
|
||||||
import {Link} from '#/components/Link'
|
|
||||||
import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRight} from '#/components/icons/SquareArrowTopRight'
|
|
||||||
// import {getAgent} from '#/state/session'
|
|
||||||
|
|
||||||
export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
|
export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
|
||||||
|
|
||||||
export type ReportDialogLabelIds = LabelGroupDefinition['id'] | 'other'
|
import {atoms as a} from '#/alf'
|
||||||
export type ReportDialogProps = {
|
import {Loader} from '#/components/Loader'
|
||||||
control: Dialog.DialogOuterProps['control']
|
import * as Dialog from '#/components/Dialog'
|
||||||
params:
|
|
||||||
| {
|
|
||||||
type: 'content'
|
|
||||||
uri: string
|
|
||||||
cid: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'profile'
|
|
||||||
did: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function LabelGroupButton({
|
import {ReportDialogProps} from './types'
|
||||||
name,
|
import {SelectReportOptionView} from './SelectReportOptionView'
|
||||||
description,
|
import {SubmitView} from './SubmitView'
|
||||||
}: {
|
|
||||||
name: string
|
|
||||||
description: string
|
|
||||||
}) {
|
|
||||||
const t = useTheme()
|
|
||||||
const {hovered, focused, pressed} = useButtonContext()
|
|
||||||
const interacted = hovered || focused || pressed
|
|
||||||
|
|
||||||
const styles = React.useMemo(() => {
|
|
||||||
return {
|
|
||||||
interacted: {
|
|
||||||
backgroundColor: t.palette.contrast_50,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}, [t])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.w_full,
|
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_between,
|
|
||||||
a.p_md,
|
|
||||||
a.rounded_md,
|
|
||||||
{paddingRight: 70},
|
|
||||||
interacted && styles.interacted,
|
|
||||||
]}>
|
|
||||||
<View style={[a.flex_1, a.gap_xs]}>
|
|
||||||
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
|
|
||||||
{name}
|
|
||||||
</Text>
|
|
||||||
<Text style={[a.leading_tight, {maxWidth: 400}]}>{description}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.absolute,
|
|
||||||
a.inset_0,
|
|
||||||
a.justify_center,
|
|
||||||
a.pr_md,
|
|
||||||
{left: 'auto'},
|
|
||||||
]}>
|
|
||||||
<ChevronRight
|
|
||||||
size="md"
|
|
||||||
fill={
|
|
||||||
hovered ? t.palette.primary_500 : t.atoms.text_contrast_low.color
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function LabelerToggle({title}: {title: string}) {
|
|
||||||
const t = useTheme()
|
|
||||||
const ctx = Toggle.useItemContext()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.py_md,
|
|
||||||
a.px_xl,
|
|
||||||
a.rounded_full,
|
|
||||||
a.overflow_hidden,
|
|
||||||
t.atoms.bg_contrast_25,
|
|
||||||
]}>
|
|
||||||
{ctx.selected && <GradientFill gradient={tokens.gradients.midnight} />}
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_between,
|
|
||||||
a.gap_lg,
|
|
||||||
a.z_10,
|
|
||||||
]}>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
native({marginTop: 2}),
|
|
||||||
{
|
|
||||||
color:
|
|
||||||
t.name === 'light' && ctx.selected
|
|
||||||
? t.palette.white
|
|
||||||
: t.atoms.text.color,
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
<Plus
|
|
||||||
size="sm"
|
|
||||||
fill={
|
|
||||||
ctx.selected
|
|
||||||
? t.palette.primary_200
|
|
||||||
: t.atoms.text_contrast_low.color
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function SubmitView({
|
|
||||||
params,
|
|
||||||
selectedLabelGroup,
|
|
||||||
goBack,
|
|
||||||
onSubmitComplete,
|
|
||||||
}: ReportDialogProps & {
|
|
||||||
selectedLabelGroup: ReportDialogLabelIds
|
|
||||||
goBack: () => void
|
|
||||||
onSubmitComplete: () => void
|
|
||||||
}) {
|
|
||||||
const t = useTheme()
|
|
||||||
const {_} = useLingui()
|
|
||||||
const labelGroupStrings = useLabelGroupStrings()
|
|
||||||
const groupInfoStrings = labelGroupStrings[selectedLabelGroup]
|
|
||||||
const [details, setDetails] = React.useState<string>('')
|
|
||||||
const [submitting, setSubmitting] = React.useState<boolean>(false)
|
|
||||||
const supportedLabelers = [] //labelGroupToLabelerMap[selectedLabelGroup]
|
|
||||||
const [selectedServices, setSelectedServices] = React.useState<string[]>(
|
|
||||||
supportedLabelers?.map(labeler => labeler.creator.did) || [],
|
|
||||||
)
|
|
||||||
|
|
||||||
const submit = React.useCallback(async () => {
|
|
||||||
setSubmitting(true)
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
||||||
|
|
||||||
const $type =
|
|
||||||
params.type === 'content'
|
|
||||||
? 'com.atproto.repo.strongRef'
|
|
||||||
: 'com.atproto.admin.defs#repoRef'
|
|
||||||
const report = {
|
|
||||||
reasonType: selectedLabelGroup, // TODO map to reasons
|
|
||||||
subject: {
|
|
||||||
$type,
|
|
||||||
...params,
|
|
||||||
},
|
|
||||||
reason: details,
|
|
||||||
}
|
|
||||||
console.log(report)
|
|
||||||
// await getAgent().createModerationReport(report)
|
|
||||||
|
|
||||||
setSubmitting(false)
|
|
||||||
|
|
||||||
Toast.show(`Thank you. Your report has been sent.`)
|
|
||||||
|
|
||||||
onSubmitComplete()
|
|
||||||
}, [params, details, selectedLabelGroup, onSubmitComplete])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[a.gap_2xl]}>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="solid"
|
|
||||||
color="secondary"
|
|
||||||
shape="round"
|
|
||||||
label={_(msg`Go back to previous step`)}
|
|
||||||
onPress={goBack}>
|
|
||||||
<ButtonIcon icon={ChevronLeft} />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.w_full,
|
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_between,
|
|
||||||
a.gap_lg,
|
|
||||||
a.p_md,
|
|
||||||
a.rounded_md,
|
|
||||||
t.atoms.bg_contrast_25,
|
|
||||||
]}>
|
|
||||||
<View style={[a.flex_1, a.gap_xs]}>
|
|
||||||
<Text style={[a.text_md, a.font_bold]}>{groupInfoStrings.name}</Text>
|
|
||||||
<Text style={[a.leading_tight, {maxWidth: 400}]}>
|
|
||||||
{groupInfoStrings.description}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Check size="md" style={[a.pr_sm]} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.gap_md]}>
|
|
||||||
<Text style={[t.atoms.text_contrast_medium]}>
|
|
||||||
Select the moderation service(s) to report to
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{supportedLabelers ? (
|
|
||||||
<Toggle.Group
|
|
||||||
label="Select mod services"
|
|
||||||
values={selectedServices}
|
|
||||||
onChange={setSelectedServices}>
|
|
||||||
<View style={[a.flex_row, a.gap_sm, a.flex_wrap]}>
|
|
||||||
{supportedLabelers.map(labeler => {
|
|
||||||
const title = getModerationServiceTitle({
|
|
||||||
displayName: labeler.creator.displayName,
|
|
||||||
handle: labeler.creator.handle,
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<Toggle.Item
|
|
||||||
key={labeler.creator.did}
|
|
||||||
name={labeler.creator.did}
|
|
||||||
label={title}>
|
|
||||||
<LabelerToggle title={title} />
|
|
||||||
</Toggle.Item>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
</Toggle.Group>
|
|
||||||
) : (
|
|
||||||
<View style={[a.p_lg, a.rounded_sm, t.atoms.bg_contrast_25]}>
|
|
||||||
<Text style={[a.italic, t.atoms.text_contrast_medium]}>
|
|
||||||
None of your subscribed labelers support this content type.
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<View style={[a.gap_md]}>
|
|
||||||
<Text style={[t.atoms.text_contrast_medium]}>
|
|
||||||
Optionally provide additional information below:
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<View style={[a.relative, a.w_full]}>
|
|
||||||
<Dialog.Input
|
|
||||||
multiline
|
|
||||||
value={details}
|
|
||||||
onChangeText={setDetails}
|
|
||||||
label="Text field"
|
|
||||||
style={{paddingRight: 60}}
|
|
||||||
numberOfLines={6}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.absolute,
|
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
|
||||||
a.pr_md,
|
|
||||||
a.pb_sm,
|
|
||||||
{
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<CharProgress count={details?.length || 0} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.flex_row, a.align_center, a.justify_end, a.gap_lg]}>
|
|
||||||
{!selectedServices.length && (
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
a.italic,
|
|
||||||
a.leading_snug,
|
|
||||||
t.atoms.text_contrast_medium,
|
|
||||||
]}>
|
|
||||||
You must select at least one labeler for a report
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
size="large"
|
|
||||||
variant="solid"
|
|
||||||
color="primary"
|
|
||||||
label={_(msg`Submit`)}
|
|
||||||
onPress={submit}
|
|
||||||
disabled={!selectedServices.length}>
|
|
||||||
<ButtonText>Submit</ButtonText>
|
|
||||||
{submitting && <ButtonIcon icon={Loader} />}
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ReportDialogLoaded({
|
export function ReportDialogLoaded({
|
||||||
labelGroupToLabelerMap,
|
|
||||||
...props
|
...props
|
||||||
}: ReportDialogProps & {
|
}: ReportDialogProps & {
|
||||||
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
|
labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
|
||||||
labelGroupToLabelerMap: ReturnType<typeof getLabelGroupToLabelerMap>
|
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
|
||||||
const {_} = useLingui()
|
|
||||||
const control = Dialog.useDialogControl()
|
const control = Dialog.useDialogControl()
|
||||||
const [selectedLabelGroup, setSelectedLabelGroup] = React.useState<
|
const [selectedReportOption, setSelectedReportOption] = React.useState<
|
||||||
ReportDialogLabelIds | undefined
|
ReportOption | undefined
|
||||||
>()
|
>()
|
||||||
const labelGroupStrings = useLabelGroupStrings()
|
|
||||||
const contentGroups = useConfigurableContentLabelGroups()
|
|
||||||
const profileGroups = useConfigurableProfileLabelGroups()
|
|
||||||
const groups = props.params.type === 'content' ? contentGroups : profileGroups
|
|
||||||
const filteredGroups = groups.filter(group => {
|
|
||||||
return Boolean(labelGroupToLabelerMap[group.id])
|
|
||||||
})
|
|
||||||
|
|
||||||
const i18n = React.useMemo(() => {
|
const onSelectReportOption = React.useCallback(
|
||||||
let title = _(msg`Report this post`)
|
(reportOption: ReportOption) => {
|
||||||
let description = _(msg`Why should this post be reviewed?`)
|
if (reportOption.reason === 'copyright') {
|
||||||
|
|
||||||
if (props.params.type === 'profile') {
|
|
||||||
title = _(msg`Report this user`)
|
|
||||||
description = _(msg`Why should this user be reviewed?`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
}
|
|
||||||
}, [_, props.params.type])
|
|
||||||
|
|
||||||
const next = React.useCallback(
|
|
||||||
(group: ReportDialogLabelIds | 'copyright') => {
|
|
||||||
if (group === 'copyright') {
|
|
||||||
Linking.openURL(DMCA_LINK)
|
Linking.openURL(DMCA_LINK)
|
||||||
} else {
|
} else {
|
||||||
setSelectedLabelGroup(group)
|
setSelectedReportOption(reportOption)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setSelectedLabelGroup],
|
[setSelectedReportOption],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{selectedLabelGroup ? (
|
{selectedReportOption ? (
|
||||||
<SubmitView
|
<SubmitView
|
||||||
{...props}
|
{...props}
|
||||||
labelGroupToLabelerMap={labelGroupToLabelerMap}
|
selectedReportOption={selectedReportOption}
|
||||||
selectedLabelGroup={selectedLabelGroup}
|
goBack={() => setSelectedReportOption(undefined)}
|
||||||
goBack={() => setSelectedLabelGroup(undefined)}
|
|
||||||
onSubmitComplete={control.close}
|
onSubmitComplete={control.close}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<View style={[a.gap_lg]}>
|
<SelectReportOptionView
|
||||||
<View style={[a.justify_center, a.gap_sm]}>
|
{...props}
|
||||||
<Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
|
onSelectReportOption={onSelectReportOption}
|
||||||
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
|
||||||
{i18n.description}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<View style={[a.gap_sm, {marginHorizontal: a.p_md.padding * -1}]}>
|
|
||||||
{filteredGroups.map(def => {
|
|
||||||
const strings = labelGroupStrings[def.id]
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={def.id}
|
|
||||||
label={_(msg`Create report for ${strings.name}`)}
|
|
||||||
onPress={() => next(def.id)}>
|
|
||||||
<LabelGroupButton
|
|
||||||
name={strings.name}
|
|
||||||
description={strings.description}
|
|
||||||
/>
|
/>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
label={_(msg`Create report for other reasons`)}
|
|
||||||
onPress={() => next('other')}>
|
|
||||||
<LabelGroupButton
|
|
||||||
name="Other"
|
|
||||||
description="An issue not covered by another option"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{props.params.type === 'content' && (
|
|
||||||
<View style={[a.pt_md, a.px_md]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_between,
|
|
||||||
a.gap_lg,
|
|
||||||
a.p_md,
|
|
||||||
a.pl_lg,
|
|
||||||
a.rounded_md,
|
|
||||||
t.atoms.bg_contrast_900,
|
|
||||||
]}>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
t.atoms.text_inverted,
|
|
||||||
a.italic,
|
|
||||||
a.leading_snug,
|
|
||||||
]}>
|
|
||||||
Need to report a copyright violation?
|
|
||||||
</Text>
|
|
||||||
<Link
|
|
||||||
to={DMCA_LINK}
|
|
||||||
label={_(
|
|
||||||
msg`View details for reporting a copyright violation`,
|
|
||||||
)}
|
|
||||||
size="small"
|
|
||||||
variant="solid"
|
|
||||||
color="secondary">
|
|
||||||
<ButtonText>View details</ButtonText>
|
|
||||||
<ButtonIcon position="right" icon={SquareArrowTopRight} />
|
|
||||||
</Link>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ReportDialogInner(props: ReportDialogProps) {
|
function ReportDialogInner(props: ReportDialogProps) {
|
||||||
const {
|
const {isLoading, data: labelers, error} = useMyLabelers()
|
||||||
isLoading: isPreferencesLoading,
|
|
||||||
error: preferencesError,
|
|
||||||
data: preferences,
|
|
||||||
} = usePreferencesQuery()
|
|
||||||
const {
|
|
||||||
isLoading: isLabelersLoading,
|
|
||||||
data: labelers,
|
|
||||||
error: labelersError,
|
|
||||||
} = useLabelersDetailedInfoQuery({
|
|
||||||
dids: preferences ? preferences.moderationPrefs.mods.map(m => m.did) : [],
|
|
||||||
})
|
|
||||||
const isLoading = isPreferencesLoading || isLabelersLoading
|
|
||||||
const error = preferencesError || labelersError
|
|
||||||
|
|
||||||
const [fakeLoading, setFakeLoading] = React.useState(isLoading)
|
const [fakeLoading, setFakeLoading] = React.useState(isLoading)
|
||||||
|
|
||||||
const labelGroupToLabelerMap = React.useMemo(() => {
|
|
||||||
if (!labelers) return {}
|
|
||||||
return getLabelGroupToLabelerMap(labelers)
|
|
||||||
}, [labelers])
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// on initial load, show a loading spinner for a hot sec to prevent flash
|
// on initial load, show a loading spinner for a hot sec to prevent flash
|
||||||
if (fakeLoading) setTimeout(() => setFakeLoading(false), 500)
|
if (fakeLoading) setTimeout(() => setFakeLoading(false), 500)
|
||||||
@@ -506,12 +73,8 @@ function ReportDialogInner(props: ReportDialogProps) {
|
|||||||
{/* Here to capture focus for a hot sec to prevent flash */}
|
{/* Here to capture focus for a hot sec to prevent flash */}
|
||||||
<Pressable accessible={false} />
|
<Pressable accessible={false} />
|
||||||
</View>
|
</View>
|
||||||
) : error || !(preferences && labelers) ? null : ( // TODO
|
) : error || !labelers ? null : ( // TODO
|
||||||
<ReportDialogLoaded
|
<ReportDialogLoaded {...props} labelers={labelers} />
|
||||||
{...props}
|
|
||||||
labelers={labelers}
|
|
||||||
labelGroupToLabelerMap={labelGroupToLabelerMap}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Dialog.ScrollableInner>
|
</Dialog.ScrollableInner>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
|
||||||
|
export type ReportDialogProps = {
|
||||||
|
control: Dialog.DialogOuterProps['control']
|
||||||
|
params:
|
||||||
|
| {
|
||||||
|
type: 'post' | 'list' | 'other'
|
||||||
|
uri: string
|
||||||
|
cid: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'account'
|
||||||
|
did: string
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,21 +25,6 @@ export function isJustAMute(modui: ModerationUI): boolean {
|
|||||||
return modui.filters.length === 1 && modui.filters[0].type === 'muted'
|
return modui.filters.length === 1 && modui.filters[0].type === 'muted'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useConfigurableContentLabelGroups() {
|
|
||||||
// TODO removeme
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useConfigurableProfileLabelGroups() {
|
|
||||||
// TODO removeme
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useConfigurableAccountLabelGroups() {
|
|
||||||
// TODO removeme
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getModerationServiceTitle({
|
export function getModerationServiceTitle({
|
||||||
displayName,
|
displayName,
|
||||||
handle,
|
handle,
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
import {LABEL_GROUPS} from '@atproto/api'
|
|
||||||
import {msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {useMemo} from 'react'
|
|
||||||
|
|
||||||
export type LabelGroupStrings = Record<
|
|
||||||
keyof typeof LABEL_GROUPS | 'other',
|
|
||||||
{name: string; description: string}
|
|
||||||
>
|
|
||||||
|
|
||||||
export function useLabelGroupStrings(): LabelGroupStrings {
|
|
||||||
const {_} = useLingui()
|
|
||||||
return useMemo(
|
|
||||||
() => ({
|
|
||||||
system: {
|
|
||||||
name: _(msg`System`),
|
|
||||||
description: _(msg`Moderator overrides for special cases.`),
|
|
||||||
},
|
|
||||||
legal: {
|
|
||||||
name: _(msg`Legal`),
|
|
||||||
description: _(msg`Content removed for legal reasons.`),
|
|
||||||
},
|
|
||||||
'intellectual-property': {
|
|
||||||
name: _(msg`Intellectual Property`),
|
|
||||||
description: _(msg`Plagiarism, copying without attribution.`),
|
|
||||||
},
|
|
||||||
porn: {
|
|
||||||
name: _(msg`Explicit Sexual Images`),
|
|
||||||
description: _(msg`i.e. pornography.`),
|
|
||||||
},
|
|
||||||
suggestive: {
|
|
||||||
name: _(msg`Sexually Suggestive`),
|
|
||||||
description: _(msg`Does not include nudity.`),
|
|
||||||
},
|
|
||||||
nudity: {
|
|
||||||
name: _(msg`Other Nudity`),
|
|
||||||
description: _(msg`Including non-sexual and artistic.`),
|
|
||||||
},
|
|
||||||
violence: {
|
|
||||||
name: _(msg`Violent / Bloody`),
|
|
||||||
description: _(msg`Gore, self-harm, torture.`),
|
|
||||||
},
|
|
||||||
'drugs-alcohol': {
|
|
||||||
name: _(msg`Substance Abuse`),
|
|
||||||
description: _(msg`Use of drugs or alcohol.`),
|
|
||||||
},
|
|
||||||
'self-harm': {
|
|
||||||
name: _(msg`Self Harm`),
|
|
||||||
description: _(msg`Suicide, self-harm, eating disorders.`),
|
|
||||||
},
|
|
||||||
intolerance: {
|
|
||||||
name: _(msg`Intolerance`),
|
|
||||||
description: _(
|
|
||||||
msg`Content or behavior which is hateful or intolerant toward a group of people.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'bad-behavior': {
|
|
||||||
name: _(msg`Bad Behavior`),
|
|
||||||
description: _(
|
|
||||||
msg`Harassment, bullying, and threats toward other users.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
rude: {
|
|
||||||
name: _(msg`Rude`),
|
|
||||||
description: _(msg`Behavior which is rude toward other users.`),
|
|
||||||
},
|
|
||||||
upsetting: {
|
|
||||||
name: _(msg`Upsetting`),
|
|
||||||
description: _(
|
|
||||||
msg`Shocking, disgusting, or generally upsetting content.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
troubling: {
|
|
||||||
name: _(msg`Troubling`),
|
|
||||||
description: _(
|
|
||||||
msg`Bad news, troubling information, or dispiriting content.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'hate-group-mention': {
|
|
||||||
name: _(msg`Hate Group Coverage`),
|
|
||||||
description: _(
|
|
||||||
msg`Images of terror groups, articles covering events, etc.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
discourse: {
|
|
||||||
name: _(msg`Discourse / Drama`),
|
|
||||||
description: _(
|
|
||||||
msg`On-going discussions or debates that may be frustrating.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
curation: {
|
|
||||||
name: _(msg`Curation`),
|
|
||||||
description: _(
|
|
||||||
msg`Judgment of the moderators to remove content not worth showing.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
spam: {
|
|
||||||
name: _(msg`Spam`),
|
|
||||||
description: _(msg`Content which doesn't add to the conversation.`),
|
|
||||||
},
|
|
||||||
misrepresentation: {
|
|
||||||
name: _(msg`Misrepresentation`),
|
|
||||||
description: _(msg`Impersonations, scams.`),
|
|
||||||
},
|
|
||||||
security: {
|
|
||||||
name: _(msg`Security`),
|
|
||||||
description: _(msg`Potential security attacks.`),
|
|
||||||
},
|
|
||||||
misinfo: {
|
|
||||||
name: _(msg`Misinformation`),
|
|
||||||
description: _(msg`Content which misleads or defrauds users.`),
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
name: _(msg`Context`),
|
|
||||||
description: _(
|
|
||||||
msg`Helpful annotations to explain intent, such as satire or parody.`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
bot: {
|
|
||||||
name: _(msg`Bots`),
|
|
||||||
description: _(msg`Automated accounts which follow the rules.`),
|
|
||||||
},
|
|
||||||
other: {
|
|
||||||
name: _(msg`Other`),
|
|
||||||
description: _(msg`Other content not covered by the other categories.`),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[_],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {useMemo} from 'react'
|
||||||
|
import {ComAtprotoModerationDefs} from '@atproto/api'
|
||||||
|
|
||||||
|
export interface ReportOption {
|
||||||
|
reason: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReportOptions {
|
||||||
|
account: ReportOption[]
|
||||||
|
post: ReportOption[]
|
||||||
|
list: ReportOption[]
|
||||||
|
other: ReportOption[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useReportOptions(): ReportOptions {
|
||||||
|
const {_} = useLingui()
|
||||||
|
return useMemo(() => {
|
||||||
|
const other = {
|
||||||
|
reason: ComAtprotoModerationDefs.REASONOTHER,
|
||||||
|
title: _(msg`Other`),
|
||||||
|
description: _(msg`An issue not included in these options`),
|
||||||
|
}
|
||||||
|
const common = [
|
||||||
|
{
|
||||||
|
reason: ComAtprotoModerationDefs.REASONRUDE,
|
||||||
|
title: _(msg`Anti-Social Behavior`),
|
||||||
|
description: _(msg`Harassment, trolling, or intolerance`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reason: ComAtprotoModerationDefs.REASONVIOLATION,
|
||||||
|
title: _(msg`Illegal and Urgent`),
|
||||||
|
description: _(msg`Glaring violations of law or terms of service`),
|
||||||
|
},
|
||||||
|
other,
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
account: [
|
||||||
|
{
|
||||||
|
reason: ComAtprotoModerationDefs.REASONMISLEADING,
|
||||||
|
title: _(msg`Misleading Account`),
|
||||||
|
description: _(
|
||||||
|
msg`Impersonation or false claims about identity or affiliation`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reason: ComAtprotoModerationDefs.REASONSPAM,
|
||||||
|
title: _(msg`Frequently Posts Unwanted Content`),
|
||||||
|
description: _(msg`Spam; excessive mentions or replies`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reason: ComAtprotoModerationDefs.REASONVIOLATION,
|
||||||
|
title: _(msg`Name or Description Violates Community Standards`),
|
||||||
|
description: _(msg`Terms used violate community standards`),
|
||||||
|
},
|
||||||
|
other,
|
||||||
|
],
|
||||||
|
post: [
|
||||||
|
{
|
||||||
|
reason: ComAtprotoModerationDefs.REASONSPAM,
|
||||||
|
title: _(msg`Spam`),
|
||||||
|
description: _(msg`Excessive mentions or replies`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reason: ComAtprotoModerationDefs.REASONSEXUAL,
|
||||||
|
title: _(msg`Unwanted Sexual Content`),
|
||||||
|
description: _(msg`Nudity or pornography not labeled as such`),
|
||||||
|
},
|
||||||
|
...common,
|
||||||
|
],
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
reason: ComAtprotoModerationDefs.REASONVIOLATION,
|
||||||
|
title: _(msg`Name or Description Violates Community Standards`),
|
||||||
|
description: _(msg`Terms used violate community standards`),
|
||||||
|
},
|
||||||
|
...common,
|
||||||
|
],
|
||||||
|
other: common,
|
||||||
|
}
|
||||||
|
}, [_])
|
||||||
|
}
|
||||||
@@ -270,7 +270,7 @@ export function ProfileHeaderDropdownBtn({
|
|||||||
<>
|
<>
|
||||||
<ReportDialog
|
<ReportDialog
|
||||||
control={control}
|
control={control}
|
||||||
params={{type: 'profile', did: profile.did}}
|
params={{type: 'account', did: profile.did}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NativeDropdown
|
<NativeDropdown
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {track} from '#/lib/analytics/analytics'
|
|||||||
import {getAge} from '#/lib/strings/time'
|
import {getAge} from '#/lib/strings/time'
|
||||||
import {getAgent, useSession} from '#/state/session'
|
import {getAgent, useSession} from '#/state/session'
|
||||||
import {
|
import {
|
||||||
ConfigurableLabelGroup,
|
|
||||||
UsePreferencesQueryResponse,
|
UsePreferencesQueryResponse,
|
||||||
ThreadViewPreferences,
|
ThreadViewPreferences,
|
||||||
} from '#/state/queries/preferences/types'
|
} from '#/state/queries/preferences/types'
|
||||||
|
|||||||
@@ -94,18 +94,22 @@ export function useMyLabelers() {
|
|||||||
dids.push(BSKY_LABELER_DID)
|
dids.push(BSKY_LABELER_DID)
|
||||||
}
|
}
|
||||||
const labelers = useLabelersDetailedInfoQuery({dids})
|
const labelers = useLabelersDetailedInfoQuery({dids})
|
||||||
return labelers.data || []
|
return {
|
||||||
|
isLoading: prefs.isLoading || labelers.isLoading,
|
||||||
|
error: prefs.error || labelers.error,
|
||||||
|
data: labelers.data,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLabelDefinitions() {
|
export function useLabelDefinitions() {
|
||||||
const labelers = useMyLabelers()
|
const labelers = useMyLabelers()
|
||||||
return {
|
return {
|
||||||
labelDefs: Object.fromEntries(
|
labelDefs: Object.fromEntries(
|
||||||
labelers.map(labeler => [
|
(labelers.data || []).map(labeler => [
|
||||||
labeler.creator.did,
|
labeler.creator.did,
|
||||||
interpretLabelValueDefinitions(labeler),
|
interpretLabelValueDefinitions(labeler),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
labelers,
|
labelers: labelers.data || [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ let PostDropdownBtn = ({
|
|||||||
<ReportDialog
|
<ReportDialog
|
||||||
control={control}
|
control={control}
|
||||||
params={{
|
params={{
|
||||||
type: 'content',
|
type: 'post',
|
||||||
uri: postUri,
|
uri: postUri,
|
||||||
cid: postCid,
|
cid: postCid,
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user