Add start of post-hiding by labels

This commit is contained in:
Paul Frazee
2023-04-11 22:44:53 -07:00
parent 866a5da0f0
commit b730b7c81f
8 changed files with 240 additions and 69 deletions
+115
View File
@@ -0,0 +1,115 @@
export const FILTER_SETTINGS = {
base: {
sourceDids: [
'did:plc:ar7c4by46qjdydhdevvrndac',
'did:plc:jonz5u6ulzdkwpnhqlwbsbrh',
],
filterLabelVals: ['csam', 'dmca-violation', 'nudity-nonconsentual'],
},
'bsky-unfiltered': {
sourceDids: [
'did:plc:ar7c4by46qjdydhdevvrndac',
'did:plc:jonz5u6ulzdkwpnhqlwbsbrh',
],
filterLabelVals: [],
},
'bsky-default': {
sourceDids: [
'did:plc:ar7c4by46qjdydhdevvrndac',
'did:plc:jonz5u6ulzdkwpnhqlwbsbrh',
],
filterLabelVals: ['porn', 'nudity', 'gore', 'self-harm', 'torture', 'spam'],
},
'bsky-calm': {
sourceDids: [
'did:plc:ar7c4by46qjdydhdevvrndac',
'did:plc:jonz5u6ulzdkwpnhqlwbsbrh',
],
filterLabelVals: [
'porn',
'nudity',
'sexual',
'gore',
'self-harm',
'torture',
'icon-kkk',
'icon-nazi',
'icon-confederate',
'spam',
'impersonation',
],
},
}
export const FILTERS = {
'bsky-unfiltered': {
title: 'Unfiltered',
author: 'Bluesky Team',
description: 'Show me everything.',
filterLabelVals: FILTER_SETTINGS.base.filterLabelVals.concat(
FILTER_SETTINGS['bsky-unfiltered'].filterLabelVals,
),
},
'bsky-default': {
title: 'Default',
author: 'Bluesky Team',
description:
'Filters out NSFW and nudity, gore, self-harm, torture, and spam.',
filterLabelVals: FILTER_SETTINGS.base.filterLabelVals.concat(
FILTER_SETTINGS['bsky-default'].filterLabelVals,
),
},
'bsky-calm': {
title: 'Calm',
author: 'Bluesky Team',
description:
'Like default, but also filters out hate-group iconography and impersonations.',
filterLabelVals: FILTER_SETTINGS.base.filterLabelVals.concat(
FILTER_SETTINGS['bsky-calm'].filterLabelVals,
),
},
}
export interface LabelValGroup {
id: string
title: string
values: string[]
}
export const LABEL_VAL_GROUPS: Record<string, LabelValGroup> = {
illegal: {
id: 'illegal',
title: 'Illegal Content',
values: ['csam', 'dmca-violation', 'nudity-nonconsentual'],
},
nsfw: {
id: 'nsfw',
title: 'Sexual Content',
values: ['porn', 'nudity', 'sexual'],
},
gore: {
id: 'gore',
title: 'Violent / Bloody Content',
values: ['gore', 'self-harm', 'torture'],
},
hate: {
id: 'hate',
title: 'Political Hate-Group Content',
values: ['icon-kkk', 'icon-nazi', 'icon-confederate'],
},
spam: {
id: 'spam',
title: 'Spam',
values: ['spam'],
},
impersonation: {
id: 'impersonation',
title: 'Impersonated Content',
values: ['impersonation'],
},
unknown: {
id: 'unknown',
title: 'Unknown Label',
values: [],
},
}
+11
View File
@@ -0,0 +1,11 @@
import {ComAtprotoLabelDefs} from '@atproto/api'
import {LabelValGroup, LABEL_VAL_GROUPS} from './const'
export function getLabelValueGroup(labelVal: string): LabelValGroup {
for (const id in LABEL_VAL_GROUPS) {
if (LABEL_VAL_GROUPS[id].values.includes(labelVal)) {
return LABEL_VAL_GROUPS[id]
}
}
return LABEL_VAL_GROUPS.unknown
}
+3
View File
@@ -6,6 +6,7 @@ import {
AppBskyFeedRepost,
AppBskyFeedLike,
AppBskyGraphFollow,
ComAtprotoLabelDefs,
} from '@atproto/api'
import AwaitLock from 'await-lock'
import {bundleAsync} from 'lib/async/bundle'
@@ -49,6 +50,7 @@ export class NotificationsFeedItemModel {
record?: SupportedRecord
isRead: boolean = false
indexedAt: string = ''
labels?: ComAtprotoLabelDefs.Label[]
additional?: NotificationsFeedItemModel[]
// additional data
@@ -73,6 +75,7 @@ export class NotificationsFeedItemModel {
this.record = this.toSupportedRecord(v.record)
this.isRead = v.isRead
this.indexedAt = v.indexedAt
this.labels = v.labels
if (v.additional?.length) {
this.additional = []
for (const add of v.additional) {
+5 -3
View File
@@ -22,7 +22,7 @@ import {useStores} from 'state/index'
import {PostMeta} from '../util/PostMeta'
import {PostEmbeds} from '../util/post-embeds'
import {PostCtrls} from '../util/PostCtrls'
import {PostMutedWrapper} from '../util/PostMuted'
import {PostHider} from '../util/PostHider'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {usePalette} from 'lib/hooks/usePalette'
@@ -270,7 +270,9 @@ export const PostThreadItem = observer(function PostThreadItem({
)
} else {
return (
<PostMutedWrapper isMuted={item.post.author.viewer?.muted === true}>
<PostHider
isMuted={item.post.author.viewer?.muted === true}
labels={item.post.labels}>
<Link
testID={`postThreadItem-by-${item.post.author.handle}`}
style={[styles.outer, {borderTopColor: pal.colors.border}, pal.view]}
@@ -364,7 +366,7 @@ export const PostThreadItem = observer(function PostThreadItem({
/>
</Link>
) : undefined}
</PostMutedWrapper>
</PostHider>
)
}
})
+5 -3
View File
@@ -17,7 +17,7 @@ import {UserInfoText} from '../util/UserInfoText'
import {PostMeta} from '../util/PostMeta'
import {PostEmbeds} from '../util/post-embeds'
import {PostCtrls} from '../util/PostCtrls'
import {PostMutedWrapper} from '../util/PostMuted'
import {PostHider} from '../util/PostHider'
import {Text} from '../util/text/Text'
import {RichText} from '../util/text/RichText'
import * as Toast from '../util/Toast'
@@ -150,7 +150,9 @@ export const Post = observer(function Post({
}
return (
<PostMutedWrapper isMuted={item.post.author.viewer?.muted === true}>
<PostHider
isMuted={item.post.author.viewer?.muted === true}
labels={item.post.labels}>
<Link
style={[styles.outer, pal.view, pal.border, style]}
href={itemHref}
@@ -227,7 +229,7 @@ export const Post = observer(function Post({
</View>
</View>
</Link>
</PostMutedWrapper>
</PostHider>
)
})
+18 -13
View File
@@ -14,7 +14,7 @@ import {UserInfoText} from '../util/UserInfoText'
import {PostMeta} from '../util/PostMeta'
import {PostCtrls} from '../util/PostCtrls'
import {PostEmbeds} from '../util/post-embeds'
import {PostMutedWrapper} from '../util/PostMuted'
import {PostHider} from '../util/PostHider'
import {RichText} from '../util/text/RichText'
import * as Toast from '../util/Toast'
import {UserAvatar} from '../util/UserAvatar'
@@ -59,7 +59,7 @@ export const FeedItem = observer(function ({
return urip.hostname
}, [record?.reply])
const onPressReply = () => {
const onPressReply = React.useCallback(() => {
track('FeedItem:PostReply')
store.shell.openComposer({
replyTo: {
@@ -73,29 +73,34 @@ export const FeedItem = observer(function ({
},
},
})
}
const onPressToggleRepost = () => {
}, [item, track, record, store])
const onPressToggleRepost = React.useCallback(() => {
track('FeedItem:PostRepost')
return item
.toggleRepost()
.catch(e => store.log.error('Failed to toggle repost', e))
}
const onPressToggleLike = () => {
}, [track, item, store])
const onPressToggleLike = React.useCallback(() => {
track('FeedItem:PostLike')
return item
.toggleLike()
.catch(e => store.log.error('Failed to toggle like', e))
}
const onCopyPostText = () => {
}, [track, item, store])
const onCopyPostText = React.useCallback(() => {
Clipboard.setString(record?.text || '')
Toast.show('Copied to clipboard')
}
}, [record])
const onOpenTranslate = React.useCallback(() => {
Linking.openURL(
encodeURI(`https://translate.google.com/#auto|en|${record?.text || ''}`),
)
}, [record])
const onDeletePost = () => {
const onDeletePost = React.useCallback(() => {
track('FeedItem:PostDelete')
item.delete().then(
() => {
@@ -107,7 +112,7 @@ export const FeedItem = observer(function ({
Toast.show('Failed to delete post, please try again')
},
)
}
}, [track, item, setDeleted, store])
if (!record || deleted) {
return <View />
@@ -127,7 +132,7 @@ export const FeedItem = observer(function ({
]
return (
<PostMutedWrapper isMuted={isMuted}>
<PostHider isMuted={isMuted} labels={item.post.labels}>
<Link
testID={`feedItem-by-${item.post.author.handle}`}
style={outerStyles}
@@ -257,7 +262,7 @@ export const FeedItem = observer(function ({
</View>
</View>
</Link>
</PostMutedWrapper>
</PostHider>
)
})
+83
View File
@@ -0,0 +1,83 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {ComAtprotoLabelDefs} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {usePalette} from 'lib/hooks/usePalette'
import {Text} from './text/Text'
import {getLabelValueGroup} from 'lib/labeling/helpers'
export function PostHider({
isMuted,
labels,
children,
}: React.PropsWithChildren<{
isMuted?: boolean
labels: ComAtprotoLabelDefs.Label[] | undefined
}>) {
const pal = usePalette('default')
const palError = usePalette('error')
const [override, setOverride] = React.useState(false)
if (!isMuted && !labels?.length) {
return <>{children}</>
}
const label = labels?.[0] // TODO use config to settle on most relevant item
const labelGroup = getLabelValueGroup(label?.val || '')
if (labelGroup.id === 'illegal') {
return <></>
}
return (
<View style={[styles.container, pal.view]}>
<View style={[styles.description, pal.view, pal.border]}>
<FontAwesomeIcon
icon={['far', 'eye-slash']}
style={[styles.icon, pal.text]}
/>
<Text type="md" style={pal.textLight}>
{isMuted ? (
<>Post from an account you muted.</>
) : label ? (
<>Warning: {labelGroup.title}</>
) : (
''
)}
</Text>
<TouchableOpacity
style={styles.showBtn}
onPress={() => setOverride(v => !v)}>
<Text type="md" style={pal.link}>
{override ? 'Hide' : 'Show'} post
</Text>
</TouchableOpacity>
</View>
{override && (
<View
style={[styles.childrenContainer, pal.border, palError.viewLight]}>
{children}
</View>
)}
</View>
)
}
const styles = StyleSheet.create({
description: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 14,
paddingHorizontal: 18,
borderTopWidth: 1,
},
icon: {
marginRight: 10,
},
showBtn: {
marginLeft: 'auto',
},
childrenContainer: {
paddingHorizontal: 6,
paddingVertical: 6,
},
})
-50
View File
@@ -1,50 +0,0 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {usePalette} from 'lib/hooks/usePalette'
import {Text} from './text/Text'
export function PostMutedWrapper({
isMuted,
children,
}: React.PropsWithChildren<{isMuted?: boolean}>) {
const pal = usePalette('default')
const [override, setOverride] = React.useState(false)
if (!isMuted || override) {
return <>{children}</>
}
return (
<View style={[styles.container, pal.view, pal.border]}>
<FontAwesomeIcon
icon={['far', 'eye-slash']}
style={[styles.icon, pal.text]}
/>
<Text type="md" style={pal.textLight}>
Post from an account you muted.
</Text>
<TouchableOpacity
style={styles.showBtn}
onPress={() => setOverride(true)}>
<Text type="md" style={pal.link}>
Show post
</Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 14,
paddingHorizontal: 18,
borderTopWidth: 1,
},
icon: {
marginRight: 10,
},
showBtn: {
marginLeft: 'auto',
},
})