Labeling & moderation updates [DRAFT] (#1057)
* First pass moving to the new labeling sdk (it compiles) * Correct behaviors around interpreting label moderation * Improve moderation state rendering * Improve hiders and alerts * Improve handling of mutes * Improve profile warnings * Add profile blurring to profile header * Add blocks to test cases * Render labels on profile cards, do not filter * Filter profiles from suggestions using moderation * Apply profile blurring to ProfileCard * Handle blocked and deleted quote posts * Temporarily translate content filtering settings to new labels * Fix types * Tune ContentHider & PostHider click targets * Put a warning on profilecard label pills * Fix screenhider learnmore link on mobile * Enforce no-override on user avatar * Dont enumerate profile blur-media labels in alerts * Fixes to muted posts (esp quotes of muted users) * Fixes to account/profile warnings * Bump @atproto/api@0.5.0 * Bump @atproto/api@0.5.1 * Fix tests * 1.43 * Remove log * Bump @atproto/api@0.5.2
This commit is contained in:
parent
3ae5a6b631
commit
b154d3ea21
43 changed files with 1193 additions and 717 deletions
|
@ -1,7 +1,11 @@
|
|||
import * as React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
moderateProfile,
|
||||
ProfileModeration,
|
||||
} from '@atproto/api'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
|
@ -11,12 +15,11 @@ import {useStores} from 'state/index'
|
|||
import {FollowButton} from './FollowButton'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {
|
||||
getProfileViewBasicLabelInfo,
|
||||
getProfileModeration,
|
||||
} from 'lib/labeling/helpers'
|
||||
import {ModerationBehaviorCode} from 'lib/labeling/types'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {
|
||||
describeModerationCause,
|
||||
getProfileModerationCauses,
|
||||
} from 'lib/moderation'
|
||||
|
||||
export const ProfileCard = observer(
|
||||
({
|
||||
|
@ -25,7 +28,6 @@ export const ProfileCard = observer(
|
|||
noBg,
|
||||
noBorder,
|
||||
followers,
|
||||
overrideModeration,
|
||||
renderButton,
|
||||
}: {
|
||||
testID?: string
|
||||
|
@ -33,7 +35,6 @@ export const ProfileCard = observer(
|
|||
noBg?: boolean
|
||||
noBorder?: boolean
|
||||
followers?: AppBskyActorDefs.ProfileView[] | undefined
|
||||
overrideModeration?: boolean
|
||||
renderButton?: (
|
||||
profile: AppBskyActorDefs.ProfileViewBasic,
|
||||
) => React.ReactNode
|
||||
|
@ -41,18 +42,11 @@ export const ProfileCard = observer(
|
|||
const store = useStores()
|
||||
const pal = usePalette('default')
|
||||
|
||||
const moderation = getProfileModeration(
|
||||
store,
|
||||
getProfileViewBasicLabelInfo(profile),
|
||||
const moderation = moderateProfile(
|
||||
profile,
|
||||
store.preferences.moderationOpts,
|
||||
)
|
||||
|
||||
if (
|
||||
moderation.list.behavior === ModerationBehaviorCode.Hide &&
|
||||
!overrideModeration
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
testID={testID}
|
||||
|
@ -82,20 +76,17 @@ export const ProfileCard = observer(
|
|||
lineHeight={1.2}>
|
||||
{sanitizeDisplayName(
|
||||
profile.displayName || sanitizeHandle(profile.handle),
|
||||
moderation.profile,
|
||||
)}
|
||||
</Text>
|
||||
<Text type="md" style={[pal.textLight]} numberOfLines={1}>
|
||||
{sanitizeHandle(profile.handle, '@')}
|
||||
</Text>
|
||||
{!!profile.viewer?.followedBy && (
|
||||
<View style={s.flexRow}>
|
||||
<View style={[s.mt5, pal.btn, styles.pill]}>
|
||||
<Text type="xs" style={pal.text}>
|
||||
Follows You
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<ProfileCardPills
|
||||
followedBy={!!profile.viewer?.followedBy}
|
||||
moderation={moderation}
|
||||
/>
|
||||
{!!profile.viewer?.followedBy && <View style={s.flexRow} />}
|
||||
</View>
|
||||
{renderButton ? (
|
||||
<View style={styles.layoutButton}>{renderButton(profile)}</View>
|
||||
|
@ -114,6 +105,44 @@ export const ProfileCard = observer(
|
|||
},
|
||||
)
|
||||
|
||||
function ProfileCardPills({
|
||||
followedBy,
|
||||
moderation,
|
||||
}: {
|
||||
followedBy: boolean
|
||||
moderation: ProfileModeration
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
|
||||
const causes = getProfileModerationCauses(moderation)
|
||||
if (!followedBy && !causes.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.pills}>
|
||||
{followedBy && (
|
||||
<View style={[s.mt5, pal.btn, styles.pill]}>
|
||||
<Text type="xs" style={pal.text}>
|
||||
Follows You
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{causes.map(cause => {
|
||||
const desc = describeModerationCause(cause, 'account')
|
||||
return (
|
||||
<View style={[s.mt5, pal.btn, styles.pill]}>
|
||||
<Text type="xs" style={pal.text}>
|
||||
{cause?.type === 'label' ? '⚠' : ''}
|
||||
{desc.name}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const FollowersList = observer(
|
||||
({followers}: {followers?: AppBskyActorDefs.ProfileView[] | undefined}) => {
|
||||
const store = useStores()
|
||||
|
@ -125,9 +154,9 @@ const FollowersList = observer(
|
|||
const followersWithMods = followers
|
||||
.map(f => ({
|
||||
f,
|
||||
mod: getProfileModeration(store, getProfileViewBasicLabelInfo(f)),
|
||||
mod: moderateProfile(f, store.preferences.moderationOpts),
|
||||
}))
|
||||
.filter(({mod}) => mod.list.behavior !== ModerationBehaviorCode.Hide)
|
||||
.filter(({mod}) => !mod.account.filter)
|
||||
|
||||
return (
|
||||
<View style={styles.followedBy}>
|
||||
|
@ -218,6 +247,12 @@ const styles = StyleSheet.create({
|
|||
paddingRight: 10,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
pills: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
columnGap: 6,
|
||||
rowGap: 2,
|
||||
},
|
||||
pill: {
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: 6,
|
||||
|
|
|
@ -21,15 +21,13 @@ import * as Toast from '../util/Toast'
|
|||
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {ThemedText} from '../util/text/ThemedText'
|
||||
import {TextLink} from '../util/Link'
|
||||
import {RichText} from '../util/text/RichText'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {UserBanner} from '../util/UserBanner'
|
||||
import {ProfileHeaderWarnings} from '../util/moderation/ProfileHeaderWarnings'
|
||||
import {ProfileHeaderAlerts} from '../util/moderation/ProfileHeaderAlerts'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {listUriToHref} from 'lib/strings/url-helpers'
|
||||
import {isDesktopWeb, isNative} from 'platform/detection'
|
||||
import {FollowState} from 'state/models/cache/my-follows'
|
||||
import {shareUrl} from 'lib/sharing'
|
||||
|
@ -116,7 +114,10 @@ const ProfileHeaderLoaded = observer(
|
|||
}, [navigation])
|
||||
|
||||
const onPressAvi = React.useCallback(() => {
|
||||
if (view.avatar) {
|
||||
if (
|
||||
view.avatar &&
|
||||
!(view.moderation.avatar.blur && view.moderation.avatar.noOverride)
|
||||
) {
|
||||
store.shell.openLightbox(new ProfileImageLightbox(view))
|
||||
}
|
||||
}, [store, view])
|
||||
|
@ -434,6 +435,7 @@ const ProfileHeaderLoaded = observer(
|
|||
style={[pal.text, styles.title]}>
|
||||
{sanitizeDisplayName(
|
||||
view.displayName || sanitizeHandle(view.handle),
|
||||
view.moderation.profile,
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
|
@ -494,7 +496,9 @@ const ProfileHeaderLoaded = observer(
|
|||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
{view.descriptionRichText ? (
|
||||
{view.description &&
|
||||
view.descriptionRichText &&
|
||||
!view.moderation.profile.blur ? (
|
||||
<RichText
|
||||
testID="profileHeaderDescription"
|
||||
style={[styles.description, pal.text]}
|
||||
|
@ -504,52 +508,7 @@ const ProfileHeaderLoaded = observer(
|
|||
) : undefined}
|
||||
</>
|
||||
)}
|
||||
<ProfileHeaderWarnings moderation={view.moderation.view} />
|
||||
<View style={styles.moderationLines}>
|
||||
{view.viewer.blocking ? (
|
||||
<View
|
||||
testID="profileHeaderBlockedNotice"
|
||||
style={[styles.moderationNotice, pal.viewLight]}>
|
||||
<FontAwesomeIcon icon="ban" style={[pal.text]} />
|
||||
<Text type="lg-medium" style={pal.text}>
|
||||
Account blocked
|
||||
</Text>
|
||||
</View>
|
||||
) : view.viewer.muted ? (
|
||||
<View
|
||||
testID="profileHeaderMutedNotice"
|
||||
style={[styles.moderationNotice, pal.viewLight]}>
|
||||
<FontAwesomeIcon
|
||||
icon={['far', 'eye-slash']}
|
||||
style={[pal.text]}
|
||||
/>
|
||||
<Text type="lg-medium" style={pal.text}>
|
||||
Account muted{' '}
|
||||
{view.viewer.mutedByList && (
|
||||
<Text type="lg-medium" style={pal.text}>
|
||||
by{' '}
|
||||
<TextLink
|
||||
type="lg-medium"
|
||||
style={pal.link}
|
||||
href={listUriToHref(view.viewer.mutedByList.uri)}
|
||||
text={view.viewer.mutedByList.name}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
) : undefined}
|
||||
{view.viewer.blockedBy && (
|
||||
<View
|
||||
testID="profileHeaderBlockedNotice"
|
||||
style={[styles.moderationNotice, pal.viewLight]}>
|
||||
<FontAwesomeIcon icon="ban" style={[pal.text]} />
|
||||
<Text type="lg-medium" style={pal.text}>
|
||||
This account has blocked you
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<ProfileHeaderAlerts moderation={view.moderation} />
|
||||
</View>
|
||||
{!isDesktopWeb && !hideBackButton && (
|
||||
<TouchableWithoutFeedback
|
||||
|
@ -693,19 +652,6 @@ const styles = StyleSheet.create({
|
|||
paddingVertical: 2,
|
||||
},
|
||||
|
||||
moderationLines: {
|
||||
gap: 6,
|
||||
},
|
||||
|
||||
moderationNotice: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 14,
|
||||
gap: 8,
|
||||
},
|
||||
|
||||
br40: {borderRadius: 40},
|
||||
br50: {borderRadius: 50},
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue