diff --git a/src/components/ModerationLabelPref/SimpleModerationLabelPref.tsx b/src/components/ModerationLabelPref/SimpleModerationLabelPref.tsx index 550ebdb559..d44a801711 100644 --- a/src/components/ModerationLabelPref/SimpleModerationLabelPref.tsx +++ b/src/components/ModerationLabelPref/SimpleModerationLabelPref.tsx @@ -4,7 +4,7 @@ import {InterprettedLabelValueDefinition, LabelPreference} from '@atproto/api' import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' -import {useLabelStrings} from '#/lib/moderation/useLabelStrings' +import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' import { usePreferencesQuery, usePreferencesSetContentLabelMutation, @@ -30,10 +30,10 @@ export function SimpleModerationLabelPref({ const savedPref = preferences?.moderationPrefs.labels[identifier] const pref = variables?.visibility ?? savedPref ?? 'warn' - const allLabelStrings = useLabelStrings() + const allLabelStrings = useGlobalLabelStrings() const labelStrings = labelValueDefinition.identifier in allLabelStrings - ? allLabelStrings[labelValueDefinition.identifier].general + ? allLabelStrings[labelValueDefinition.identifier] : { name: labelValueDefinition.identifier, description: `Labeled "${labelValueDefinition.identifier}"`, diff --git a/src/components/ModerationLabelPref/index.tsx b/src/components/ModerationLabelPref/index.tsx index 1d61d7a7c9..a73bfd9d2c 100644 --- a/src/components/ModerationLabelPref/index.tsx +++ b/src/components/ModerationLabelPref/index.tsx @@ -4,7 +4,7 @@ import {InterprettedLabelValueDefinition, LabelPreference} from '@atproto/api' import {useLingui} from '@lingui/react' import {msg, Trans} from '@lingui/macro' -import {useLabelStrings} from '#/lib/moderation/useLabelStrings' +import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' import { useLabelBehaviorDescription, useLabelLongBehaviorDescription, @@ -58,11 +58,11 @@ export function ModerationLabelPref({ 'ignore', ) - const allLabelStrings = useLabelStrings() + const allLabelStrings = useGlobalLabelStrings() const labelStrings = labelValueDefinition.locales[0] // TODO look up locale ? labelValueDefinition.locales[0] : labelValueDefinition.identifier in allLabelStrings - ? allLabelStrings[labelValueDefinition.identifier].general + ? allLabelStrings[labelValueDefinition.identifier] : { name: labelValueDefinition.identifier, description: `Labeled "${labelValueDefinition.identifier}"`, diff --git a/src/components/ModerationServiceCard/index.tsx b/src/components/ModerationServiceCard/index.tsx index 6279f6251f..e0c07732ca 100644 --- a/src/components/ModerationServiceCard/index.tsx +++ b/src/components/ModerationServiceCard/index.tsx @@ -11,12 +11,12 @@ import {useLabelerInfoQuery} from '#/state/queries/labeler' export * as Card from '#/components/ModerationServiceCard/Card' type ModerationServiceProps = { - modservice: AppBskyLabelerDefs.LabelerViewDetailed + labeler: AppBskyLabelerDefs.LabelerViewDetailed } export function Link({ children, - modservice, + labeler, }: ModerationServiceProps & Pick) { const {_} = useLingui() @@ -25,11 +25,11 @@ export function Link({ to={{ screen: 'Profile', params: { - name: modservice.creator.handle, + name: labeler.creator.handle, }, }} label={_( - msg`View the moderation service provided by @${modservice.creator.handle}`, + msg`View the moderation service provided by @${labeler.creator.handle}`, )}> {children} @@ -54,7 +54,7 @@ export function Loader({ loading?: React.ComponentType<{}> error?: React.ComponentType<{error: string}> component: React.ComponentType<{ - modservice: AppBskyLabelerDefs.LabelerViewDetailed + labeler: AppBskyLabelerDefs.LabelerViewDetailed }> }) { const {isLoading, data, error} = useLabelerInfoQuery({did}) @@ -68,6 +68,6 @@ export function Loader({ ) : null ) : ( - + ) } diff --git a/src/components/ReportDialog/index.tsx b/src/components/ReportDialog/index.tsx index 3166874f0c..210cf45a71 100644 --- a/src/components/ReportDialog/index.tsx +++ b/src/components/ReportDialog/index.tsx @@ -112,7 +112,7 @@ function LabelGroupButton({ ) } -function ModServiceToggle({title}: {title: string}) { +function LabelerToggle({title}: {title: string}) { const t = useTheme() const ctx = Toggle.useItemContext() @@ -260,7 +260,7 @@ function SubmitView({ key={labeler.creator.did} name={labeler.creator.did} label={title}> - + ) })} @@ -477,21 +477,21 @@ function ReportDialogInner(props: ReportDialogProps) { data: preferences, } = usePreferencesQuery() const { - isLoading: isModServicesLoading, - data: modservices, - error: modservicesError, + isLoading: isLabelersLoading, + data: labelers, + error: labelersError, } = useLabelersDetailedInfoQuery({ - dids: preferences ? preferences.moderationOpts.mods.map(m => m.did) : [], + dids: preferences ? preferences.moderationPrefs.mods.map(m => m.did) : [], }) - const isLoading = isPreferencesLoading || isModServicesLoading - const error = preferencesError || modservicesError + const isLoading = isPreferencesLoading || isLabelersLoading + const error = preferencesError || labelersError const [fakeLoading, setFakeLoading] = React.useState(isLoading) const labelGroupToLabelerMap = React.useMemo(() => { - if (!modservices) return {} - return getLabelGroupToLabelerMap(modservices) - }, [modservices]) + if (!labelers) return {} + return getLabelGroupToLabelerMap(labelers) + }, [labelers]) React.useEffect(() => { // on initial load, show a loading spinner for a hot sec to prevent flash @@ -506,10 +506,10 @@ function ReportDialogInner(props: ReportDialogProps) { {/* Here to capture focus for a hot sec to prevent flash */} - ) : error || !(preferences && modservices) ? null : ( // TODO + ) : error || !(preferences && labelers) ? null : ( // TODO )} diff --git a/src/components/moderation/ContentHider.tsx b/src/components/moderation/ContentHider.tsx new file mode 100644 index 0000000000..d6e7a9c37c --- /dev/null +++ b/src/components/moderation/ContentHider.tsx @@ -0,0 +1,130 @@ +import React from 'react' +import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' +import {ModerationUI} from '@atproto/api' +import {useLingui} from '@lingui/react' +import {msg, Trans} from '@lingui/macro' + +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {isJustAMute} from '#/lib/moderation' +import {sanitizeDisplayName} from '#/lib/strings/display-names' + +import {atoms as a, useTheme, useBreakpoints} from '#/alf' +import {Button, ButtonText, ButtonIcon} from '#/components/Button' +import {Text} from '#/components/Typography' +import { + ModerationDetailsDialog, + useModerationDetailsDialogControl, +} from '#/components/moderation/ModerationDetailsDialog' + +export function ContentHider({ + testID, + modui, + ignoreMute, + style, + childContainerStyle, + children, +}: React.PropsWithChildren<{ + testID?: string + modui: ModerationUI | undefined + ignoreMute?: boolean + style?: StyleProp + childContainerStyle?: StyleProp +}>) { + const t = useTheme() + const {_} = useLingui() + const [override, setOverride] = React.useState(false) + const {gtMobile} = useBreakpoints() + const control = useModerationDetailsDialogControl() + + const blur = modui?.blurs[0] + const desc = useModerationCauseDescription(blur) + + if (!blur || (ignoreMute && isJustAMute(modui))) { + return ( + + {children} + + ) + } + + return ( + + + + + {desc.source && blur.type === 'label' && !override && ( + + )} + {override && {children}} + + ) +} + +const styles = StyleSheet.create({ + outer: { + overflow: 'hidden', + }, + cover: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + borderRadius: 8, + marginTop: 4, + paddingVertical: 14, + paddingLeft: 14, + paddingRight: 18, + }, + showBtn: { + marginLeft: 'auto', + alignSelf: 'center', + }, +}) diff --git a/src/view/com/util/moderation/LabelsOnMe.tsx b/src/components/moderation/LabelsOnMe.tsx similarity index 97% rename from src/view/com/util/moderation/LabelsOnMe.tsx rename to src/components/moderation/LabelsOnMe.tsx index cf8313d07c..6554c888af 100644 --- a/src/view/com/util/moderation/LabelsOnMe.tsx +++ b/src/components/moderation/LabelsOnMe.tsx @@ -11,7 +11,7 @@ import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/ico import { LabelsOnMeDialog, useLabelsOnMeDialogControl, -} from '#/components/LabelsOnMeDialog' +} from '#/components/moderation/LabelsOnMeDialog' export function LabelsOnMe({ details, diff --git a/src/components/LabelsOnMeDialog.tsx b/src/components/moderation/LabelsOnMeDialog.tsx similarity index 58% rename from src/components/LabelsOnMeDialog.tsx rename to src/components/moderation/LabelsOnMeDialog.tsx index 9364baccc5..497ab6a4a7 100644 --- a/src/components/LabelsOnMeDialog.tsx +++ b/src/components/moderation/LabelsOnMeDialog.tsx @@ -4,11 +4,15 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {ComAtprotoLabelDefs} from '@atproto/api' +import {useLabelInfo} from '#/lib/moderation/useLabelInfo' +import {makeProfileLink} from '#/lib/routes/links' +import {sanitizeHandle} from '#/lib/strings/handles' + import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Text} from '#/components/Typography' import * as Dialog from '#/components/Dialog' -import {Button} from '#/components/Button' -import {capitalize} from '#/lib/strings/capitalize' +import {Button, ButtonText} from '#/components/Button' +import {InlineLink} from '#/components/Link' export {useDialogControl as useLabelsOnMeDialogControl} from '#/components/Dialog' @@ -61,12 +65,13 @@ export function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) { - - The following labels were applied to your{' '} - {isAccount ? 'account' : 'content'} - + {isAccount ? ( + Labels on your account + ) : ( + Labels on your content + )} - + You may appeal these labels if you feel they were placed in error. @@ -74,11 +79,11 @@ export function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) { {labels.map(label => ( - - {capitalize(label.val)} - + @@ -106,3 +111,49 @@ export function LabelsOnMeDialog(props: LabelsOnMeDialogProps) { ) } + +function Label({ + label, + control, +}: { + label: ComAtprotoLabelDefs.Label + control: Dialog.DialogOuterProps['control'] +}) { + const t = useTheme() + const {labeler, strings} = useLabelInfo(label) + return ( + + + + {strings.name} + + {strings.description} + control.close()} + style={[]}> + {labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src} + + + + + + + ) +} diff --git a/src/components/ModerationDetailsDialog.tsx b/src/components/moderation/ModerationDetailsDialog.tsx similarity index 74% rename from src/components/ModerationDetailsDialog.tsx rename to src/components/moderation/ModerationDetailsDialog.tsx index e5e3927b23..0f2eb53636 100644 --- a/src/components/ModerationDetailsDialog.tsx +++ b/src/components/moderation/ModerationDetailsDialog.tsx @@ -4,19 +4,20 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {ModerationCause} from '@atproto/api' -import {atoms as a, useBreakpoints} from '#/alf' +import {listUriToHref} from '#/lib/strings/url-helpers' +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' + +import {useTheme, atoms as a, useBreakpoints} from '#/alf' import {Text} from '#/components/Typography' import * as Dialog from '#/components/Dialog' import {Button} from '#/components/Button' import {InlineLink} from '#/components/Link' -import {useLabelStrings} from '#/lib/moderation/useLabelStrings' -import {listUriToHref} from '#/lib/strings/url-helpers' +import {makeProfileLink} from '#/lib/routes/links' export {useDialogControl as useModerationDetailsDialogControl} from '#/components/Dialog' export interface ModerationDetailsDialogProps { control: Dialog.DialogOuterProps['control'] - context: 'account' | 'content' modcause: ModerationCause } @@ -24,21 +25,20 @@ export function ModerationDetailsDialog(props: ModerationDetailsDialogProps) { return ( - ) } function ModerationDetailsDialogInner({ - context, modcause, + control, }: ModerationDetailsDialogProps & { control: Dialog.DialogOuterProps['control'] }) { + const t = useTheme() const {_} = useLingui() - const labelStrings = useLabelStrings() - const control = Dialog.useDialogControl() + const desc = useModerationCauseDescription(modcause) const {gtMobile} = useBreakpoints() let name @@ -95,13 +95,8 @@ function ModerationDetailsDialogInner({ description = _(msg`You have muted this user.`) } } else if (modcause.type === 'label') { - if (modcause.labelDef.id in labelStrings) { - name = labelStrings[modcause.labelDef.id][context].name - description = labelStrings[modcause.labelDef.id][context].description - } else { - name = modcause.labelDef.id - description = _(msg`Labeled ${modcause.labelDef.id}`) - } + name = desc.name + description = desc.description } else { // should never happen name = '' @@ -112,12 +107,38 @@ function ModerationDetailsDialogInner({ - + {name} - + {description} + {modcause.type === 'label' && ( + + + + This label was applied by{' '} + control.close()}> + {desc.source} + + . + + + + )} - + ) } @@ -78,7 +69,7 @@ function PostInform({cause}: {cause: ModerationCause}) { function PostAlert({cause}: {cause: ModerationCause}) { const t = useTheme() const control = useModerationDetailsDialogControl() - const desc = useModerationCauseDescription(cause, 'content') + const desc = useModerationCauseDescription(cause) return ( <> @@ -91,21 +82,19 @@ function PostAlert({cause}: {cause: ModerationCause}) { onPress={() => { control.open() }}> - + {desc.name} - - {' — ' /* TODO get actual labeler */} - Bluesky Safety - + {desc.source && ( + + {' — '} + {sanitizeDisplayName(desc.source)} + + )} - + ) } diff --git a/src/view/com/util/moderation/PostHider.tsx b/src/components/moderation/PostHider.tsx similarity index 63% rename from src/view/com/util/moderation/PostHider.tsx rename to src/components/moderation/PostHider.tsx index aed29e0b41..464ee20778 100644 --- a/src/view/com/util/moderation/PostHider.tsx +++ b/src/components/moderation/PostHider.tsx @@ -1,20 +1,20 @@ import React, {ComponentProps} from 'react' import {StyleSheet, Pressable, View, ViewStyle, StyleProp} from 'react-native' import {ModerationUI} from '@atproto/api' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {usePalette} from 'lib/hooks/usePalette' -import {Link} from '../Link' -import {Text} from '../text/Text' -import {addStyle} from 'lib/styles' -import {ShieldExclamation} from 'lib/icons' import {useLingui} from '@lingui/react' import {Trans, msg} from '@lingui/macro' -import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {addStyle} from 'lib/styles' + +import {useTheme, atoms as a} from '#/alf' import { ModerationDetailsDialog, useModerationDetailsDialogControl, -} from '#/components/ModerationDetailsDialog' +} from '#/components/moderation/ModerationDetailsDialog' +import {Text} from '#/components/Typography' +// import {Link} from '#/components/Link' TODO this imposes some styles that screw things up +import {Link} from '#/view/com/util/Link' interface Props extends ComponentProps { iconSize: number @@ -32,12 +32,12 @@ export function PostHider({ iconStyles, ...props }: Props) { - const pal = usePalette('default') + const t = useTheme() const {_} = useLingui() const [override, setOverride] = React.useState(false) const control = useModerationDetailsDialogControl() const blur = modui.blurs[0] - const desc = useModerationCauseDescription(blur, 'content') + const desc = useModerationCauseDescription(blur) if (!blur) { return ( @@ -45,7 +45,6 @@ export function PostHider({ testID={testID} style={style} href={href} - noFeedback accessible={false} {...props}> {children} @@ -53,7 +52,6 @@ export function PostHider({ ) } - const isMute = blur.type === 'muted' return !override ? ( { @@ -67,15 +65,18 @@ export function PostHider({ } accessibilityLabel="" style={[ - styles.description, + a.flex_row, + a.align_center, + a.gap_sm, + a.py_md, + { + paddingLeft: 6, + paddingRight: 18, + }, override ? {paddingBottom: 0} : undefined, - pal.view, + t.atoms.bg, ]}> - + { control.open() @@ -85,32 +86,24 @@ export function PostHider({ accessibilityHint=""> - {isMute ? ( - - ) : ( - - )} + - + {desc.name} {!modui.noOverride && ( - + {override ? Hide : Show} )} @@ -120,26 +113,14 @@ export function PostHider({ testID={testID} style={addStyle(style, styles.child)} href={href} - noFeedback> + accessible={false} + {...props}> {children} ) } const styles = StyleSheet.create({ - description: { - flexDirection: 'row', - alignItems: 'center', - gap: 4, - paddingVertical: 10, - paddingLeft: 6, - paddingRight: 18, - marginTop: 1, - }, - showBtn: { - marginLeft: 'auto', - alignSelf: 'center', - }, child: { borderWidth: 0, borderTopWidth: 0, diff --git a/src/view/com/util/moderation/ProfileHeaderAlerts.tsx b/src/components/moderation/ProfileHeaderAlerts.tsx similarity index 65% rename from src/view/com/util/moderation/ProfileHeaderAlerts.tsx rename to src/components/moderation/ProfileHeaderAlerts.tsx index d866a5ecdf..f983807c8d 100644 --- a/src/view/com/util/moderation/ProfileHeaderAlerts.tsx +++ b/src/components/moderation/ProfileHeaderAlerts.tsx @@ -1,20 +1,18 @@ import React from 'react' import {StyleProp, View, ViewStyle} from 'react-native' import {ModerationCause, ModerationDecision} from '@atproto/api' -import {Text} from '../text/Text' + +import {sanitizeDisplayName} from '#/lib/strings/display-names' import {getModerationCauseKey} from 'lib/moderation' -import {Trans} from '@lingui/macro' import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText, ButtonIcon} from '#/components/Button' -import {Shield_Stroke2_Corner0_Rounded as Shield} from '#/components/icons/Shield' -import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' -import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' +import {Text} from '#/components/Typography' import { ModerationDetailsDialog, useModerationDetailsDialogControl, -} from '#/components/ModerationDetailsDialog' +} from '#/components/moderation/ModerationDetailsDialog' export function ProfileHeaderAlerts({ moderation, @@ -46,7 +44,7 @@ export function ProfileHeaderAlerts({ function ProfileInform({cause}: {cause: ModerationCause}) { const control = useModerationDetailsDialogControl() - const desc = useModerationCauseDescription(cause, 'account') + const desc = useModerationCauseDescription(cause) return ( <> @@ -59,18 +57,11 @@ function ProfileInform({cause}: {cause: ModerationCause}) { onPress={() => { control.open() }}> - {' '} + {' '} {desc.name} - + ) } @@ -78,7 +69,7 @@ function ProfileInform({cause}: {cause: ModerationCause}) { function ProfileAlert({cause}: {cause: ModerationCause}) { const t = useTheme() const control = useModerationDetailsDialogControl() - const desc = useModerationCauseDescription(cause, 'account') + const desc = useModerationCauseDescription(cause) return ( <> @@ -91,20 +82,18 @@ function ProfileAlert({cause}: {cause: ModerationCause}) { onPress={() => { control.open() }}> - + {desc.name} - - {' — ' /* TODO get actual labeler */} - Bluesky Safety - + {desc.source && ( + + {' — '} + {sanitizeDisplayName(desc.source)} + + )} - + ) } diff --git a/src/view/com/util/moderation/ScreenHider.tsx b/src/components/moderation/ScreenHider.tsx similarity index 53% rename from src/view/com/util/moderation/ScreenHider.tsx rename to src/components/moderation/ScreenHider.tsx index 26c260a8b3..71ca85a926 100644 --- a/src/view/com/util/moderation/ScreenHider.tsx +++ b/src/components/moderation/ScreenHider.tsx @@ -2,31 +2,26 @@ import React from 'react' import { TouchableWithoutFeedback, StyleProp, - StyleSheet, View, ViewStyle, } from 'react-native' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' import {useNavigation} from '@react-navigation/native' import {ModerationUI} from '@atproto/api' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {NavigationProp} from 'lib/routes/types' -import {Text} from '../text/Text' -import {Button} from '../forms/Button' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' -import {s} from '#/lib/styles' -import {CenteredView} from '../Views' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {NavigationProp} from 'lib/routes/types' +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' + +import {useTheme, atoms as a} from '#/alf' +import {CenteredView} from '#/view/com/util/Views' +import {Text} from '#/components/Typography' +import {Button, ButtonText} from '#/components/Button' import { ModerationDetailsDialog, useModerationDetailsDialogControl, -} from '#/components/ModerationDetailsDialog' +} from '#/components/moderation/ModerationDetailsDialog' export function ScreenHider({ testID, @@ -42,15 +37,14 @@ export function ScreenHider({ style?: StyleProp containerStyle?: StyleProp }>) { - const pal = usePalette('default') - const palInverted = usePalette('inverted') + const t = useTheme() const {_} = useLingui() const [override, setOverride] = React.useState(false) const navigation = useNavigation() const {isMobile} = useWebMediaQueries() const control = useModerationDetailsDialogControl() const blur = modui.blurs[0] - const desc = useModerationCauseDescription(blur, 'content') + const desc = useModerationCauseDescription(blur) if (!blur || override) { return ( @@ -66,25 +60,53 @@ export function ScreenHider({ ) return ( - - - + + + - + {isNoPwi ? ( Sign-in Required ) : ( Content Warning )} - + {isNoPwi ? ( This account has requested that users sign in to view their profile. @@ -92,7 +114,7 @@ export function ScreenHider({ ) : ( <> This {screenDescription} has been flagged: - + {desc.name}.{' '} - + Learn More - + )}{' '} - {isMobile && } - + {isMobile && } + {!modui.noOverride && ( )} ) } - -const styles = StyleSheet.create({ - spacer: { - flex: 1, - }, - container: { - flex: 1, - paddingTop: 100, - paddingBottom: 150, - }, - iconContainer: { - alignItems: 'center', - marginBottom: 10, - }, - icon: { - borderRadius: 25, - width: 50, - height: 50, - alignItems: 'center', - justifyContent: 'center', - }, - title: { - textAlign: 'center', - marginBottom: 10, - }, - description: { - marginBottom: 10, - paddingHorizontal: 20, - textAlign: 'center', - }, - btnContainer: { - flexDirection: 'row', - justifyContent: 'center', - marginVertical: 10, - gap: 10, - }, - btn: { - paddingHorizontal: 20, - paddingVertical: 14, - }, -}) diff --git a/src/lib/moderation/useGlobalLabelStrings.ts b/src/lib/moderation/useGlobalLabelStrings.ts new file mode 100644 index 0000000000..b79df77ab2 --- /dev/null +++ b/src/lib/moderation/useGlobalLabelStrings.ts @@ -0,0 +1,70 @@ +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useMemo} from 'react' + +export type GlobalLabelStrings = Record< + string, + { + name: string + description: string + } +> + +export function useGlobalLabelStrings(): GlobalLabelStrings { + const {_} = useLingui() + return useMemo( + () => ({ + '!hide': { + name: _(msg`Content Blocked`), + description: _(msg`This content has been hidden by the moderators.`), + }, + '!no-promote': { + name: _(msg`Moderator Filter`), + description: _( + msg`Moderator has chosen to filter the content from feeds.`, + ), + }, + '!warn': { + name: _(msg`Content Warning`), + description: _( + msg`This content has received a general warning from moderators.`, + ), + }, + '!no-unauthenticated': { + name: _(msg`Sign-in Required`), + description: _( + msg`This user has requested that their content only be shown to signed-in users.`, + ), + }, + 'dmca-violation': { + name: _(msg`Copyright Violation`), + description: _( + msg`This content has received a DMCA takedown request. It will be restored if the concerns can be resolved.`, + ), + }, + doxxing: { + name: _(msg`Doxxing`), + description: _( + msg`This content has been reported to include private information about someone without their consent.`, + ), + }, + porn: { + name: _(msg`Pornography`), + description: _(msg`Explicit sexual images.`), + }, + sexual: { + name: _(msg`Sexually Suggestive`), + description: _(msg`Does not include nudity.`), + }, + nudity: { + name: _(msg`Nudity`), + description: _(msg`Including non-sexual and artistic.`), + }, + gore: { + name: _(msg`Violent / Bloody`), + description: _(msg`Gore, self-harm, torture`), + }, + }), + [_], + ) +} diff --git a/src/lib/moderation/useLabelInfo.ts b/src/lib/moderation/useLabelInfo.ts new file mode 100644 index 0000000000..d1dc22bcee --- /dev/null +++ b/src/lib/moderation/useLabelInfo.ts @@ -0,0 +1,99 @@ +import { + ComAtprotoLabelDefs, + AppBskyLabelerDefs, + LABELS, + interpretLabelValueDefinition, + InterprettedLabelValueDefinition, +} from '@atproto/api' +import {useLingui} from '@lingui/react' +import * as bcp47Match from 'bcp-47-match' + +import { + useGlobalLabelStrings, + GlobalLabelStrings, +} from '#/lib/moderation/useGlobalLabelStrings' +import {useLabelDefinitions} from '#/state/queries/preferences' + +export interface LabelInfo { + label: ComAtprotoLabelDefs.Label + def: InterprettedLabelValueDefinition + strings: ComAtprotoLabelDefs.LabelValueDefinitionStrings + labeler: AppBskyLabelerDefs.LabelerViewDetailed | undefined +} + +export function useLabelInfo(label: ComAtprotoLabelDefs.Label): LabelInfo { + const {i18n} = useLingui() + const globalLabelStrings = useGlobalLabelStrings() + const {labelDefs, labelers} = useLabelDefinitions() + const def = getDefinition(labelDefs, label) + return { + label, + def, + strings: getLabelStrings(i18n.locale, globalLabelStrings, def), + labeler: labelers.find(labeler => label.src === labeler.creator.did), + } +} + +export function getDefinition( + labelDefs: Record, + label: ComAtprotoLabelDefs.Label, +): InterprettedLabelValueDefinition { + // check local definitions + const customDef = + !label.val.startsWith('!') && + labelDefs[label.src].find( + def => def.identifier === label.val && def.definedBy === label.src, + ) + if (customDef) { + return customDef + } + + // check global definitions + const globalDef = LABELS[label.val as keyof typeof LABELS] + if (globalDef) { + return globalDef + } + + // fallback to a noop definition + return interpretLabelValueDefinition( + { + identifier: label.val, + severity: 'none', + blurs: 'none', + locales: [], + }, + label.src, + ) +} + +export function getLabelStrings( + locale: string, + globalLabelStrings: GlobalLabelStrings, + def: InterprettedLabelValueDefinition, +): ComAtprotoLabelDefs.LabelValueDefinitionStrings { + if (!def.definedBy) { + // global definition, look up strings + if (def.identifier in globalLabelStrings) { + return globalLabelStrings[ + def.identifier + ] as ComAtprotoLabelDefs.LabelValueDefinitionStrings + } + } else { + // try to find locale match in the definition's strings + const localeMatch = def.locales.find( + strings => bcp47Match.basicFilter(locale, strings.lang).length > 0, + ) + if (localeMatch) { + return localeMatch + } + // fall back to the zero item if no match + if (def.locales[0]) { + return def.locales[0] + } + } + return { + lang: locale, + name: def.identifier, + description: `Labeled "${def.identifier}"`, + } +} diff --git a/src/lib/moderation/useLabelStrings.ts b/src/lib/moderation/useLabelStrings.ts deleted file mode 100644 index 9f81ef5f34..0000000000 --- a/src/lib/moderation/useLabelStrings.ts +++ /dev/null @@ -1,203 +0,0 @@ -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useMemo} from 'react' - -export type LabelStrings = Record< - string, - { - general: {name: string; description: string} - account: {name: string; description: string} - content: {name: string; description: string} - } -> - -export function useLabelStrings(): LabelStrings { - const {_} = useLingui() - return useMemo( - () => ({ - '!hide': { - general: { - name: _(msg`Moderator Hide`), - description: _(msg`Moderator has chosen to hide the content.`), - }, - account: { - name: _(msg`Content Blocked`), - description: _(msg`This account has been hidden by the moderators.`), - }, - content: { - name: _(msg`Content Blocked`), - description: _(msg`This content has been hidden by the moderators.`), - }, - }, - '!no-promote': { - general: { - name: _(msg`Moderator Filter`), - description: _( - msg`Moderator has chosen to filter the content from feeds.`, - ), - }, - account: { - name: _(msg`N/A`), - description: _(msg`N/A`), - }, - content: { - name: _(msg`N/A`), - description: _(msg`N/A`), - }, - }, - '!warn': { - general: { - name: _(msg`Moderator Warn`), - description: _( - msg`Moderator has chosen to set a general warning on the content.`, - ), - }, - account: { - name: _(msg`Content Warning`), - description: _( - msg`This account has received a general warning from moderators.`, - ), - }, - content: { - name: _(msg`Content Warning`), - description: _( - msg`This content has received a general warning from moderators.`, - ), - }, - }, - '!no-unauthenticated': { - general: { - name: _(msg`Sign-in Required`), - description: _( - msg`This user has requested that their account only be shown to signed-in users.`, - ), - }, - account: { - name: _(msg`Sign-in Required`), - description: _( - msg`This user has requested that their account only be shown to signed-in users.`, - ), - }, - content: { - name: _(msg`Sign-in Required`), - description: _( - msg`This user has requested that their content only be shown to signed-in users.`, - ), - }, - }, - 'dmca-violation': { - general: { - name: _(msg`Copyright Violation`), - description: _( - msg`The content has received a DMCA takedown request.`, - ), - }, - account: { - name: _(msg`Copyright Violation`), - description: _( - msg`This account has received a DMCA takedown request. It will be restored if the concerns can be resolved.`, - ), - }, - content: { - name: _(msg`Copyright Violation`), - description: _( - msg`This content has received a DMCA takedown request. It will be restored if the concerns can be resolved.`, - ), - }, - }, - doxxing: { - general: { - name: _(msg`Doxxing`), - description: _( - msg`Information that reveals private information about someone which has been shared without the consent of the subject.`, - ), - }, - account: { - name: _(msg`Doxxing`), - description: _( - msg`This account has been reported to publish private information about someone without their consent. This report is currently under review.`, - ), - }, - content: { - name: _(msg`Doxxing`), - description: _( - msg`This content has been reported to include private information about someone without their consent.`, - ), - }, - }, - porn: { - general: { - name: _(msg`Pornography`), - description: _(msg`Explicit sexual images.`), - }, - account: { - name: _(msg`Adult Content`), - description: _( - msg`This account contains imagery of full-frontal nudity or explicit sexual activity.`, - ), - }, - content: { - name: _(msg`Adult Content`), - description: _( - msg`This content contains imagery of full-frontal nudity or explicit sexual activity.`, - ), - }, - }, - sexual: { - general: { - name: _(msg`Sexually Suggestive`), - description: _(msg`Does not include nudity.`), - }, - account: { - name: _(msg`Suggestive Content`), - description: _( - msg`This account contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress.`, - ), - }, - content: { - name: _(msg`Suggestive Content`), - description: _( - msg`This content contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress.`, - ), - }, - }, - nudity: { - general: { - name: _(msg`Nudity`), - description: _(msg`Including non-sexual and artistic.`), - }, - account: { - name: _(msg`Adult Content`), - description: _( - msg`This account contains imagery which portrays nudity in a non-sexual or artistic setting.`, - ), - }, - content: { - name: _(msg`Adult Content`), - description: _( - msg`This content contains imagery which portrays nudity in a non-sexual or artistic setting.`, - ), - }, - }, - gore: { - general: { - name: _(msg`Violent / Bloody`), - description: _(msg`Gore, self-harm, torture`), - }, - account: { - name: _(msg`Graphic Imagery (Gore)`), - description: _( - msg`This account contains shocking images involving blood or visible wounds.`, - ), - }, - content: { - name: _(msg`Graphic Imagery (Gore)`), - description: _( - msg`This content contains shocking images involving blood or visible wounds.`, - ), - }, - }, - }), - [_], - ) -} diff --git a/src/lib/moderation/useModerationCauseDescription.ts b/src/lib/moderation/useModerationCauseDescription.ts index 17283566a7..2c3c79f789 100644 --- a/src/lib/moderation/useModerationCauseDescription.ts +++ b/src/lib/moderation/useModerationCauseDescription.ts @@ -1,21 +1,31 @@ -import {ModerationCause, LABELS} from '@atproto/api' +import {ModerationCause} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useLabelStrings} from './useLabelStrings' +import {useGlobalLabelStrings} from './useGlobalLabelStrings' +import {useLabelDefinitions} from '#/state/queries/preferences' +import {getDefinition, getLabelStrings} from './useLabelInfo' + +import {TriangleExclamation_Stroke2_Corner2_Rounded as TriangleExclamation} from '#/components/icons/TriangleExclamation' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' +import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign' export interface ModerationCauseDescription { + icon: any name: string description: string + source?: string } export function useModerationCauseDescription( cause: ModerationCause | undefined, - context: 'account' | 'content', ): ModerationCauseDescription { - const {_} = useLingui() - const labelStrings = useLabelStrings() + const {_, i18n} = useLingui() + const globalLabelStrings = useGlobalLabelStrings() + const {labelDefs, labelers} = useLabelDefinitions() if (!cause) { return { + icon: TriangleExclamation, name: _(msg`Content Warning`), description: _( msg`Moderator has chosen to set a general warning on the content.`, @@ -25,6 +35,7 @@ export function useModerationCauseDescription( if (cause.type === 'blocking') { if (cause.source.type === 'list') { return { + icon: CircleBanSign, name: _(msg`User Blocked by "${cause.source.list.name}"`), description: _( msg`You have blocked this user. You cannot view their content.`, @@ -32,6 +43,7 @@ export function useModerationCauseDescription( } } else { return { + icon: CircleBanSign, name: _(msg`User Blocked`), description: _( msg`You have blocked this user. You cannot view their content.`, @@ -41,6 +53,7 @@ export function useModerationCauseDescription( } if (cause.type === 'blocked-by') { return { + icon: CircleBanSign, name: _(msg`User Blocking You`), description: _( msg`This user has blocked you. You cannot view their content.`, @@ -49,6 +62,7 @@ export function useModerationCauseDescription( } if (cause.type === 'block-other') { return { + icon: CircleBanSign, name: _(msg`Content Not Available`), description: _( msg`This content is not available because one of the users involved has blocked the other.`, @@ -58,11 +72,13 @@ export function useModerationCauseDescription( if (cause.type === 'muted') { if (cause.source.type === 'list') { return { + icon: EyeSlash, name: _(msg`Muted by "${cause.source.list.name}"`), description: _(msg`You have muted this user`), } } else { return { + icon: EyeSlash, name: _(msg`Muted User`), description: _(msg`You have muted this user`), } @@ -71,30 +87,30 @@ export function useModerationCauseDescription( // @ts-ignore Temporary extension to the moderation system -prf if (cause.type === 'hidden') { return { + icon: EyeSlash, name: _(msg`Post Hidden by You`), description: _(msg`You have hidden this post`), } } if (cause.type === 'label') { - if (cause.labelDef.identifier in labelStrings) { - const strings = - labelStrings[cause.labelDef.identifier as keyof typeof LABELS] - return { - name: - context === 'account' ? strings.account.name : strings.content.name, - description: - context === 'account' - ? strings.account.description - : strings.content.description, - } - } + const def = getDefinition(labelDefs, cause.label) + const strings = getLabelStrings(i18n.locale, globalLabelStrings, def) + const labeler = labelers.find(l => l.creator.did === cause.label.src) return { - name: cause.labelDef.identifier, - description: _(msg`Labeled ${cause.labelDef.identifier}`), + icon: + def.identifier === '!no-unauthenticated' + ? EyeSlash + : def.severity === 'alert' + ? TriangleExclamation + : CircleInfo, + name: strings.name, + description: strings.description, + source: labeler?.creator.displayName || labeler?.creator.handle, } } // should never happen return { + icon: CircleInfo, name: '', description: ``, } diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx index 5eba966383..05edeecb89 100644 --- a/src/screens/Moderation/index.tsx +++ b/src/screens/Moderation/index.tsx @@ -82,17 +82,17 @@ export function ModerationScreen( data: preferences, } = usePreferencesQuery() const { - isLoading: isModServicesLoading, - data: modservices, - error: modservicesError, + isLoading: isLabelersLoading, + data: labelers, + error: labelersError, } = useLabelersDetailedInfoQuery({ dids: preferences ? preferences.moderationPrefs.mods.map(m => m.did) : [], }) const {gtMobile} = useBreakpoints() const {height} = useSafeAreaFrame() - const isLoading = isPreferencesLoading || isModServicesLoading - const error = preferencesError || modservicesError + const isLoading = isPreferencesLoading || isLabelersLoading + const error = preferencesError || labelersError return ( - ) : error || !(preferences && modservices) ? ( + ) : error || !(preferences && labelers) ? ( ) : ( - + )} ) @@ -128,10 +125,10 @@ export function ModerationScreen( export function ModerationScreenInner({ preferences, - modservices, + labelers, }: { preferences: UsePreferencesQueryResponse - modservices: AppBskyLabelerDefs.LabelerViewDetailed[] + labelers: AppBskyLabelerDefs.LabelerViewDetailed[] }) { const t = useTheme() const setMinimalShellMode = useSetMinimalShellMode() @@ -297,25 +294,13 @@ export function ModerationScreenInner({ {adultContentEnabled && ( <> - + - + - + - + )} @@ -332,7 +317,7 @@ export function ModerationScreenInner({ - {modservices.map(mod => { + {labelers.map(mod => { return ( - <> + - + - + ) })} diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx index 5aaf4c5c3b..2d0ed77c1d 100644 --- a/src/screens/Profile/Header/Shell.tsx +++ b/src/screens/Profile/Header/Shell.tsx @@ -13,12 +13,12 @@ import {Shadow} from '#/state/cache/types' import {useLightboxControls, ProfileImageLightbox} from '#/state/lightbox' import {atoms as a, useTheme} from '#/alf' -import {LabelsOnMe} from 'view/com/util/moderation/LabelsOnMe' +import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' import {BlurView} from 'view/com/util/BlurView' import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' import {UserAvatar} from 'view/com/util/UserAvatar' import {UserBanner} from 'view/com/util/UserBanner' -import {ProfileHeaderAlerts} from 'view/com/util/moderation/ProfileHeaderAlerts' +import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' interface Props { profile: Shadow diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts index 2e5f35e16d..732d66a852 100644 --- a/src/state/queries/preferences/index.ts +++ b/src/state/queries/preferences/index.ts @@ -8,7 +8,7 @@ import { import {track} from '#/lib/analytics/analytics' import {getAge} from '#/lib/strings/time' -import {getAgent} from '#/state/session' +import {getAgent, useSession} from '#/state/session' import { ConfigurableLabelGroup, UsePreferencesQueryResponse, @@ -21,6 +21,7 @@ import { } from '#/state/queries/preferences/const' import {STALE} from '#/state/queries' import {useHiddenPosts} from '#/state/preferences/hidden-posts' +import {useLabelDefinitions} from '#/state/queries/preferences/moderation' export * from '#/state/queries/preferences/types' export * from '#/state/queries/preferences/moderation' @@ -74,7 +75,9 @@ export const moderationOptsOverrideContext = createContext< export function useModerationOpts() { const override = useContext(moderationOptsOverrideContext) + const {currentAccount} = useSession() const prefs = usePreferencesQuery() + const {labelDefs} = useLabelDefinitions() const hiddenPosts = useHiddenPosts() const opts = useMemo(() => { if (override) { @@ -85,13 +88,14 @@ export function useModerationOpts() { } const moderationPrefs = prefs.data.moderationPrefs return { - userDid: '', // TODO + userDid: currentAccount?.did, prefs: { ...moderationPrefs, hiddenPosts, }, + labelDefs, } - }, [override, prefs.data, hiddenPosts]) + }, [override, currentAccount, labelDefs, prefs.data, hiddenPosts]) return opts } diff --git a/src/state/queries/preferences/moderation.ts b/src/state/queries/preferences/moderation.ts index c26d25fef2..b2d6ff7ea5 100644 --- a/src/state/queries/preferences/moderation.ts +++ b/src/state/queries/preferences/moderation.ts @@ -1,11 +1,26 @@ -import {ComAtprotoLabelDefs, DEFAULT_LABEL_SETTINGS} from '@atproto/api' +import { + ComAtprotoLabelDefs, + AppBskyLabelerDefs, + DEFAULT_LABEL_SETTINGS, + LABELS, + BSKY_LABELER_DID, + interpretLabelValueDefinition, + interpretLabelValueDefinitions, + InterprettedLabelValueDefinition, +} from '@atproto/api' +import {useLingui} from '@lingui/react' +import * as bcp47Match from 'bcp-47-match' import { LabelGroup, ConfigurableLabelGroup, } from '#/state/queries/preferences/types' - -export type Label = ComAtprotoLabelDefs.Label +import {usePreferencesQuery} from './index' +import {useLabelersDetailedInfoQuery} from '../labeler' +import { + useGlobalLabelStrings, + GlobalLabelStrings, +} from '#/lib/moderation/useGlobalLabelStrings' export type LabelGroupConfig = { id: LabelGroup @@ -18,8 +33,6 @@ export type LabelGroupConfig = { /** * More strict than our default settings for logged in users. - * - * TODO(pwi) */ export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: typeof DEFAULT_LABEL_SETTINGS = Object.fromEntries( @@ -84,3 +97,26 @@ export const CONFIGURABLE_LABEL_GROUPS: Record< values: ['impersonation'], }, } + +export function useMyLabelers() { + const prefs = usePreferencesQuery() + const dids = prefs.data?.moderationPrefs.mods.map(m => m.did) || [] + if (!dids.includes(BSKY_LABELER_DID)) { + dids.push(BSKY_LABELER_DID) + } + const labelers = useLabelersDetailedInfoQuery({dids}) + return labelers.data || [] +} + +export function useLabelDefinitions() { + const labelers = useMyLabelers() + return { + labelDefs: Object.fromEntries( + labelers.map(labeler => [ + labeler.creator.did, + interpretLabelValueDefinitions(labeler), + ]), + ), + labelers, + } +} diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 44a93b783f..004c3ee6d4 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -23,10 +23,10 @@ import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers' import {PostMeta} from '../util/PostMeta' import {PostEmbeds} from '../util/post-embeds' 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 {PostHider} from '../../../components/moderation/PostHider' +import {ContentHider} from '../../../components/moderation/ContentHider' +import {PostAlerts} from '../../../components/moderation/PostAlerts' +import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' import {ErrorMessage} from '../util/error/ErrorMessage' import {usePalette} from 'lib/hooks/usePalette' import {formatCount} from '../util/numeric/format' diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index adf953b461..719a8592e6 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -14,9 +14,9 @@ import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' import {PostEmbeds} from '../util/post-embeds' import {PostCtrls} from '../util/post-ctrls/PostCtrls' -import {ContentHider} from '../util/moderation/ContentHider' -import {PostAlerts} from '../util/moderation/PostAlerts' -import {LabelsOnMyPost} from '../util/moderation/LabelsOnMe' +import {ContentHider} from '../../../components/moderation/ContentHider' +import {PostAlerts} from '../../../components/moderation/PostAlerts' +import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' import {Text} from '../util/text/Text' import {RichText} from '#/components/RichText' import {PreviewableUserAvatar} from '../util/UserAvatar' diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 01929d679f..737f02f8fe 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -18,9 +18,9 @@ import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' import {PostCtrls} from '../util/post-ctrls/PostCtrls' import {PostEmbeds} from '../util/post-embeds' -import {ContentHider} from '../util/moderation/ContentHider' -import {PostAlerts} from '../util/moderation/PostAlerts' -import {LabelsOnMyPost} from '../util/moderation/LabelsOnMe' +import {ContentHider} from '#/components/moderation/ContentHider' +import {PostAlerts} from '../../../components/moderation/PostAlerts' +import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe' import {RichText} from '#/components/RichText' import {PreviewableUserAvatar} from '../util/UserAvatar' import {s} from 'lib/styles' diff --git a/src/view/com/util/moderation/ContentHider.tsx b/src/view/com/util/moderation/ContentHider.tsx deleted file mode 100644 index 094740c67d..0000000000 --- a/src/view/com/util/moderation/ContentHider.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React from 'react' -import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' -import {ModerationUI} from '@atproto/api' -import {useLingui} from '@lingui/react' -import {msg, Trans} from '@lingui/macro' -import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' -import {isJustAMute} from '#/lib/moderation' - -import {atoms as a, useTheme, useBreakpoints} from '#/alf' -import {Button, ButtonText, ButtonIcon} from '#/components/Button' -import {Shield_Stroke2_Corner0_Rounded as Shield} from '#/components/icons/Shield' -import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' -import {Text} from '#/components/Typography' -import { - ModerationDetailsDialog, - useModerationDetailsDialogControl, -} from '#/components/ModerationDetailsDialog' - -export function ContentHider({ - testID, - modui, - ignoreMute, - style, - childContainerStyle, - children, -}: React.PropsWithChildren<{ - testID?: string - modui: ModerationUI | undefined - ignoreMute?: boolean - style?: StyleProp - childContainerStyle?: StyleProp -}>) { - const t = useTheme() - const {_} = useLingui() - const [override, setOverride] = React.useState(false) - const {gtMobile} = useBreakpoints() - const control = useModerationDetailsDialogControl() - - const blur = modui?.blurs[0] - const desc = useModerationCauseDescription(blur, 'content') - - if (!blur || (ignoreMute && isJustAMute(modui))) { - return ( - - {children} - - ) - } - - return ( - - - - - - {blur.type === 'label' && !override && ( - - )} - - {override && {children}} - - ) -} - -const styles = StyleSheet.create({ - outer: { - overflow: 'hidden', - }, - cover: { - flexDirection: 'row', - alignItems: 'center', - gap: 6, - borderRadius: 8, - marginTop: 4, - paddingVertical: 14, - paddingLeft: 14, - paddingRight: 18, - }, - showBtn: { - marginLeft: 'auto', - alignSelf: 'center', - }, -}) diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index ae490fb639..94c526c261 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -18,12 +18,12 @@ import {Text} from '../text/Text' import {usePalette} from 'lib/hooks/usePalette' import {ComposerOptsQuote} from 'state/shell/composer' import {PostEmbeds} from '.' -import {PostAlerts} from '../moderation/PostAlerts' +import {PostAlerts} from '../../../../components/moderation/PostAlerts' import {makeProfileLink} from 'lib/routes/links' import {InfoCircleIcon} from 'lib/icons' import {Trans} from '@lingui/macro' import {useModerationOpts} from '#/state/queries/preferences' -import {ContentHider} from '../moderation/ContentHider' +import {ContentHider} from '../../../../components/moderation/ContentHider' import {RichText} from '#/components/RichText' import {atoms as a} from '#/alf' diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index c013a98e4b..47091fbb07 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -26,7 +26,7 @@ import {MaybeQuoteEmbed} from './QuoteEmbed' import {AutoSizedImage} from '../images/AutoSizedImage' import {ListEmbed} from './ListEmbed' import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' -import {ContentHider} from '../moderation/ContentHider' +import {ContentHider} from '../../../../components/moderation/ContentHider' import {isNative} from '#/platform/detection' import {shareUrl} from '#/lib/sharing' diff --git a/src/view/screens/DebugMod.tsx b/src/view/screens/DebugMod.tsx index ca7763ab91..8fb14617fe 100644 --- a/src/view/screens/DebugMod.tsx +++ b/src/view/screens/DebugMod.tsx @@ -28,7 +28,7 @@ import { import {atoms as a, useTheme} from '#/alf' import {CenteredView, ScrollView} from '#/view/com/util/Views' import {H1, H3, P, Text} from '#/components/Typography' -import {useLabelStrings} from '#/lib/moderation/useLabelStrings' +import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' import * as Toggle from '#/components/forms/Toggle' import * as ToggleButton from '#/components/forms/ToggleButton' import {Button, ButtonIcon, ButtonText} from '#/components/Button' @@ -37,7 +37,7 @@ import { ChevronBottom_Stroke2_Corner0_Rounded as ChevronBottom, ChevronTop_Stroke2_Corner0_Rounded as ChevronTop, } from '#/components/icons/Chevron' -import {ScreenHider} from '../com/util/moderation/ScreenHider' +import {ScreenHider} from '../../components/moderation/ScreenHider' import {ProfileHeader} from '#/screens/Profile/Header' import {ProfileCard} from '../com/profile/ProfileCard' import {FeedItem} from '../com/posts/FeedItem' @@ -73,7 +73,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< ], }) const [view, setView] = React.useState(['post']) - const labelStrings = useLabelStrings() + const labelStrings = useGlobalLabelStrings() const {currentAccount} = useSession() const isTargetMe = @@ -309,7 +309,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 61675e3f33..7dae1c9639 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -12,7 +12,7 @@ import {useLingui} from '@lingui/react' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {CenteredView} from '../com/util/Views' import {ListRef} from '../com/util/List' -import {ScreenHider} from 'view/com/util/moderation/ScreenHider' +import {ScreenHider} from '#/components/moderation/ScreenHider' import {ProfileLists} from '../com/lists/ProfileLists' import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'