Add start of post-hiding by labels
This commit is contained in:
@@ -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: [],
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
@@ -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',
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user