Refactor/rewrite the entire moderation-application system

This commit is contained in:
Paul Frazee
2024-02-14 21:48:27 -08:00
parent cc635ae949
commit b009a34a7a
43 changed files with 1094 additions and 1062 deletions
+85 -44
View File
@@ -1,13 +1,15 @@
import React from 'react'
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {
AppBskyFeedDefs,
AppBskyEmbedRecord,
AppBskyFeedPost,
AppBskyEmbedImages,
AppBskyEmbedRecordWithMedia,
ModerationUI,
AppBskyEmbedExternal,
RichText as RichTextAPI,
moderatePost,
ModerationDecision,
} from '@atproto/api'
import {AtUri} from '@atproto/api'
import {PostMeta} from '../PostMeta'
@@ -21,14 +23,14 @@ import {makeProfileLink} from 'lib/routes/links'
import {InfoCircleIcon} from 'lib/icons'
import {Trans} from '@lingui/macro'
import {RichText} from 'view/com/util/text/RichText'
import {useModerationOpts} from '#/state/queries/preferences'
import {ContentHider} from '../moderation/ContentHider'
export function MaybeQuoteEmbed({
embed,
moderation,
style,
}: {
embed: AppBskyEmbedRecord.View
moderation: ModerationUI
style?: StyleProp<ViewStyle>
}) {
const pal = usePalette('default')
@@ -38,17 +40,9 @@ export function MaybeQuoteEmbed({
AppBskyFeedPost.validateRecord(embed.record.value).success
) {
return (
<QuoteEmbed
quote={{
author: embed.record.author,
cid: embed.record.cid,
uri: embed.record.uri,
indexedAt: embed.record.indexedAt,
text: embed.record.value.text,
facets: embed.record.value.facets,
embeds: embed.record.embeds,
}}
moderation={moderation}
<QuoteEmbedModerated
viewRecord={embed.record}
postRecord={embed.record.value}
style={style}
/>
)
@@ -74,19 +68,49 @@ export function MaybeQuoteEmbed({
return null
}
function QuoteEmbedModerated({
viewRecord,
postRecord,
style,
}: {
viewRecord: AppBskyEmbedRecord.ViewRecord
postRecord: AppBskyFeedPost.Record
style?: StyleProp<ViewStyle>
}) {
const moderationOpts = useModerationOpts()
const moderation = React.useMemo(() => {
return moderationOpts
? moderatePost(viewRecordToPostView(viewRecord), moderationOpts)
: undefined
}, [viewRecord, moderationOpts])
const quote = {
author: viewRecord.author,
cid: viewRecord.cid,
uri: viewRecord.uri,
indexedAt: viewRecord.indexedAt,
text: postRecord.text,
facets: postRecord.facets,
embeds: viewRecord.embeds,
}
return <QuoteEmbed quote={quote} moderation={moderation} style={style} />
}
export function QuoteEmbed({
quote,
moderation,
style,
}: {
quote: ComposerOptsQuote
moderation?: ModerationUI
moderation?: ModerationDecision
style?: StyleProp<ViewStyle>
}) {
const pal = usePalette('default')
const itemUrip = new AtUri(quote.uri)
const itemHref = makeProfileLink(quote.author, 'post', itemUrip.rkey)
const itemTitle = `Post by ${quote.author.handle}`
const richText = React.useMemo(
() =>
quote.text.trim()
@@ -94,6 +118,7 @@ export function QuoteEmbed({
: undefined,
[quote.text, quote.facets],
)
const embed = React.useMemo(() => {
const e = quote.embeds?.[0]
@@ -107,39 +132,55 @@ export function QuoteEmbed({
return e.media
}
}, [quote.embeds])
return (
<Link
style={[styles.container, pal.borderDark, style]}
hoverStyle={{borderColor: pal.colors.borderLinkHover}}
href={itemHref}
title={itemTitle}>
<View pointerEvents="none">
<PostMeta
author={quote.author}
showAvatar
authorHasWarning={false}
postHref={itemHref}
timestamp={quote.indexedAt}
/>
</View>
{moderation ? (
<PostAlerts moderation={moderation} style={styles.alert} />
) : null}
{richText ? (
<RichText
richText={richText}
type="post-text"
style={pal.text}
numberOfLines={20}
noLinks
/>
) : null}
{embed && <PostEmbeds embed={embed} moderation={{}} />}
</Link>
<ContentHider modui={moderation?.ui('contentList')}>
<Link
style={[styles.container, pal.borderDark, style]}
hoverStyle={{borderColor: pal.colors.borderLinkHover}}
href={itemHref}
title={itemTitle}>
<View pointerEvents="none">
<PostMeta
author={quote.author}
showAvatar
authorHasWarning={false}
postHref={itemHref}
timestamp={quote.indexedAt}
/>
</View>
{moderation ? (
<PostAlerts
modui={moderation.ui('contentView')}
style={styles.alert}
/>
) : null}
{richText ? (
<RichText
richText={richText}
type="post-text"
style={pal.text}
numberOfLines={20}
noLinks
/>
) : null}
{embed && <PostEmbeds embed={embed} moderation={moderation} />}
</Link>
</ContentHider>
)
}
export default QuoteEmbed
function viewRecordToPostView(
viewRecord: AppBskyEmbedRecord.ViewRecord,
): AppBskyFeedDefs.PostView {
const {value, embeds, ...rest} = viewRecord
return {
...rest,
$type: 'app.bsky.feed.defs#postView',
record: value,
embed: embeds?.[0],
}
}
const styles = StyleSheet.create({
container: {
+49 -55
View File
@@ -15,8 +15,7 @@ import {
AppBskyEmbedRecordWithMedia,
AppBskyFeedDefs,
AppBskyGraphDefs,
ModerationUI,
PostModeration,
ModerationDecision,
} from '@atproto/api'
import {Link} from '../Link'
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
@@ -26,7 +25,6 @@ import {ExternalLinkEmbed} from './ExternalLinkEmbed'
import {MaybeQuoteEmbed} from './QuoteEmbed'
import {AutoSizedImage} from '../images/AutoSizedImage'
import {ListEmbed} from './ListEmbed'
import {isCauseALabelOnUri, isQuoteBlurred} from 'lib/moderation'
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
import {ContentHider} from '../moderation/ContentHider'
import {isNative} from '#/platform/detection'
@@ -42,12 +40,10 @@ type Embed =
export function PostEmbeds({
embed,
moderation,
moderationDecisions,
style,
}: {
embed?: Embed
moderation: ModerationUI
moderationDecisions?: PostModeration['decisions']
moderation?: ModerationDecision
style?: StyleProp<ViewStyle>
}) {
const pal = usePalette('default')
@@ -66,18 +62,10 @@ export function PostEmbeds({
// quote post with media
// =
if (AppBskyEmbedRecordWithMedia.isView(embed)) {
const isModOnQuote =
(AppBskyEmbedRecord.isViewRecord(embed.record.record) &&
isCauseALabelOnUri(moderation.cause, embed.record.record.uri)) ||
(moderationDecisions && isQuoteBlurred(moderationDecisions))
const mediaModeration = isModOnQuote ? {} : moderation
const quoteModeration = isModOnQuote ? moderation : {}
return (
<View style={style}>
<PostEmbeds embed={embed.media} moderation={mediaModeration} />
<ContentHider moderation={quoteModeration}>
<MaybeQuoteEmbed embed={embed.record} moderation={quoteModeration} />
</ContentHider>
<PostEmbeds embed={embed.media} moderation={moderation} />
<MaybeQuoteEmbed embed={embed.record} />
</View>
)
}
@@ -86,6 +74,7 @@ export function PostEmbeds({
// custom feed embed (i.e. generator view)
// =
if (AppBskyFeedDefs.isGeneratorView(embed.record)) {
// TODO moderation
return (
<FeedSourceCard
feedUri={embed.record.uri}
@@ -97,16 +86,13 @@ export function PostEmbeds({
// list embed
if (AppBskyGraphDefs.isListView(embed.record)) {
// TODO moderation
return <ListEmbed item={embed.record} />
}
// quote post
// =
return (
<ContentHider moderation={moderation}>
<MaybeQuoteEmbed embed={embed} style={style} moderation={moderation} />
</ContentHider>
)
return <MaybeQuoteEmbed embed={embed} style={style} />
}
// image embed
@@ -132,35 +118,41 @@ export function PostEmbeds({
if (images.length === 1) {
const {alt, thumb, aspectRatio} = images[0]
return (
<View style={[styles.imagesContainer, style]}>
<AutoSizedImage
alt={alt}
uri={thumb}
dimensionsHint={aspectRatio}
onPress={() => _openLightbox(0)}
onPressIn={() => onPressIn(0)}
style={[styles.singleImage]}>
{alt === '' ? null : (
<View style={styles.altContainer}>
<Text style={styles.alt} accessible={false}>
ALT
</Text>
</View>
)}
</AutoSizedImage>
</View>
<ContentHider modui={moderation?.ui('contentMedia')}>
<View style={[styles.imagesContainer, style]}>
<AutoSizedImage
alt={alt}
uri={thumb}
dimensionsHint={aspectRatio}
onPress={() => _openLightbox(0)}
onPressIn={() => onPressIn(0)}
style={[styles.singleImage]}>
{alt === '' ? null : (
<View style={styles.altContainer}>
<Text style={styles.alt} accessible={false}>
ALT
</Text>
</View>
)}
</AutoSizedImage>
</View>
</ContentHider>
)
}
return (
<View style={[styles.imagesContainer, style]}>
<ImageLayoutGrid
images={embed.images}
onPress={_openLightbox}
onPressIn={onPressIn}
style={embed.images.length === 1 ? [styles.singleImage] : undefined}
/>
</View>
<ContentHider modui={moderation?.ui('contentMedia')}>
<View style={[styles.imagesContainer, style]}>
<ImageLayoutGrid
images={embed.images}
onPress={_openLightbox}
onPressIn={onPressIn}
style={
embed.images.length === 1 ? [styles.singleImage] : undefined
}
/>
</View>
</ContentHider>
)
}
}
@@ -171,15 +163,17 @@ export function PostEmbeds({
const link = embed.external
return (
<Link
asAnchor
anchorNoUnderline
href={link.uri}
style={[styles.extOuter, pal.view, pal.borderDark, style]}
hoverStyle={{borderColor: pal.colors.borderLinkHover}}
onLongPress={onShareExternal}>
<ExternalLinkEmbed link={link} />
</Link>
<ContentHider modui={moderation?.ui('contentMedia')}>
<Link
asAnchor
anchorNoUnderline
href={link.uri}
style={[styles.extOuter, pal.view, pal.borderDark, style]}
hoverStyle={{borderColor: pal.colors.borderLinkHover}}
onLongPress={onShareExternal}>
<ExternalLinkEmbed link={link} />
</Link>
</ContentHider>
)
}