Add alerts to embeds (#1138)

* Add alerts to embeds

* Add images to the mock data

* Fix types
zio/stable
Paul Frazee 2023-08-08 15:54:36 -07:00 committed by GitHub
parent e51dbefd0a
commit bbe9861eef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 80 additions and 15 deletions

View File

@ -144,7 +144,7 @@ async function main() {
await server.mocker.labelProfile('porn', 'porn-profile') await server.mocker.labelProfile('porn', 'porn-profile')
await server.mocker.labelPost( await server.mocker.labelPost(
'porn', 'porn',
await server.mocker.createPost('porn-posts', 'porn post'), await server.mocker.createImagePost('porn-posts', 'porn post'),
) )
await server.mocker.labelPost( await server.mocker.labelPost(
'porn', 'porn',
@ -167,7 +167,7 @@ async function main() {
await server.mocker.labelProfile('nudity', 'nudity-profile') await server.mocker.labelProfile('nudity', 'nudity-profile')
await server.mocker.labelPost( await server.mocker.labelPost(
'nudity', 'nudity',
await server.mocker.createPost('nudity-posts', 'nudity post'), await server.mocker.createImagePost('nudity-posts', 'nudity post'),
) )
await server.mocker.labelPost( await server.mocker.labelPost(
'nudity', 'nudity',

View File

@ -29,13 +29,13 @@ export async function createServer(
plc: {port: port2}, plc: {port: port2},
}) })
const profilePic = fs.readFileSync( const pic = fs.readFileSync(
path.join(__dirname, '..', 'assets', 'default-avatar.jpg'), path.join(__dirname, '..', 'assets', 'default-avatar.jpg'),
) )
return { return {
pdsUrl, pdsUrl,
mocker: new Mocker(pds, pdsUrl, profilePic), mocker: new Mocker(pds, pdsUrl, pic),
async close() { async close() {
await pds.server.destroy() await pds.server.destroy()
await plc.server.destroy() await plc.server.destroy()
@ -50,7 +50,7 @@ class Mocker {
constructor( constructor(
public pds: DevEnvTestPDS, public pds: DevEnvTestPDS,
public service: string, public service: string,
public profilePic: Uint8Array, public pic: Uint8Array,
) { ) {
this.agent = new BskyAgent({service}) this.agent = new BskyAgent({service})
} }
@ -90,7 +90,7 @@ class Mocker {
password: 'hunter2', password: 'hunter2',
}) })
await agent.upsertProfile(async () => { await agent.upsertProfile(async () => {
const blob = await agent.uploadBlob(this.profilePic, { const blob = await agent.uploadBlob(this.pic, {
encoding: 'image/jpeg', encoding: 'image/jpeg',
}) })
return { return {
@ -151,6 +151,25 @@ class Mocker {
}) })
} }
async createImagePost(user: string, text: string) {
const agent = this.users[user]?.agent
if (!agent) {
throw new Error(`Not a user: ${user}`)
}
const blob = await agent.uploadBlob(this.pic, {
encoding: 'image/jpeg',
})
return await agent.post({
text,
langs: ['en'],
embed: {
$type: 'app.bsky.embed.images',
images: [{image: blob.data.blob, alt: ''}],
},
createdAt: new Date().toISOString(),
})
}
async createQuotePost( async createQuotePost(
user: string, user: string,
text: string, text: string,

View File

@ -75,3 +75,13 @@ export function getProfileModerationCauses(
return true return true
}) as ModerationCause[] }) as ModerationCause[]
} }
export function isCauseALabelOnUri(
cause: ModerationCause | undefined,
uri: string,
): boolean {
if (cause?.type !== 'label') {
return false
}
return cause.label.uri === uri
}

View File

@ -269,7 +269,10 @@ export const PostThreadItem = observer(function PostThreadItem({
) : undefined} ) : undefined}
{item.post.embed && ( {item.post.embed && (
<ContentHider moderation={item.moderation.embed} style={s.mb10}> <ContentHider moderation={item.moderation.embed} style={s.mb10}>
<PostEmbeds embed={item.post.embed} /> <PostEmbeds
embed={item.post.embed}
moderation={item.moderation.embed}
/>
</ContentHider> </ContentHider>
)} )}
</ContentHider> </ContentHider>
@ -428,7 +431,10 @@ export const PostThreadItem = observer(function PostThreadItem({
) : undefined} ) : undefined}
{item.post.embed && ( {item.post.embed && (
<ContentHider style={s.mb10} moderation={item.moderation.embed}> <ContentHider style={s.mb10} moderation={item.moderation.embed}>
<PostEmbeds embed={item.post.embed} /> <PostEmbeds
embed={item.post.embed}
moderation={item.moderation.embed}
/>
</ContentHider> </ContentHider>
)} )}
{needsTranslation && ( {needsTranslation && (

View File

@ -267,7 +267,10 @@ const PostLoaded = observer(
) : undefined} ) : undefined}
{item.post.embed ? ( {item.post.embed ? (
<ContentHider moderation={item.moderation.embed} style={s.mb10}> <ContentHider moderation={item.moderation.embed} style={s.mb10}>
<PostEmbeds embed={item.post.embed} /> <PostEmbeds
embed={item.post.embed}
moderation={item.moderation.embed}
/>
</ContentHider> </ContentHider>
) : null} ) : null}
{needsTranslation && ( {needsTranslation && (

View File

@ -293,7 +293,10 @@ export const FeedItem = observer(function ({
<ContentHider <ContentHider
moderation={item.moderation.embed} moderation={item.moderation.embed}
style={styles.embed}> style={styles.embed}>
<PostEmbeds embed={item.post.embed} /> <PostEmbeds
embed={item.post.embed}
moderation={item.moderation.embed}
/>
</ContentHider> </ContentHider>
) : null} ) : null}
{needsTranslation && ( {needsTranslation && (

View File

@ -5,6 +5,7 @@ import {
AppBskyFeedPost, AppBskyFeedPost,
AppBskyEmbedImages, AppBskyEmbedImages,
AppBskyEmbedRecordWithMedia, AppBskyEmbedRecordWithMedia,
ModerationUI,
} from '@atproto/api' } from '@atproto/api'
import {AtUri} from '@atproto/api' import {AtUri} from '@atproto/api'
import {PostMeta} from '../PostMeta' import {PostMeta} from '../PostMeta'
@ -13,14 +14,17 @@ import {Text} from '../text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {ComposerOptsQuote} from 'state/models/ui/shell' import {ComposerOptsQuote} from 'state/models/ui/shell'
import {PostEmbeds} from '.' import {PostEmbeds} from '.'
import {PostAlerts} from '../moderation/PostAlerts'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {InfoCircleIcon} from 'lib/icons' import {InfoCircleIcon} from 'lib/icons'
export function MaybeQuoteEmbed({ export function MaybeQuoteEmbed({
embed, embed,
moderation,
style, style,
}: { }: {
embed: AppBskyEmbedRecord.View embed: AppBskyEmbedRecord.View
moderation: ModerationUI
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
@ -39,6 +43,7 @@ export function MaybeQuoteEmbed({
text: embed.record.value.text, text: embed.record.value.text,
embeds: embed.record.embeds, embeds: embed.record.embeds,
}} }}
moderation={moderation}
style={style} style={style}
/> />
) )
@ -66,9 +71,11 @@ export function MaybeQuoteEmbed({
export function QuoteEmbed({ export function QuoteEmbed({
quote, quote,
moderation,
style, style,
}: { }: {
quote: ComposerOptsQuote quote: ComposerOptsQuote
moderation?: ModerationUI
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
@ -100,16 +107,19 @@ export function QuoteEmbed({
postHref={itemHref} postHref={itemHref}
timestamp={quote.indexedAt} timestamp={quote.indexedAt}
/> />
{moderation ? (
<PostAlerts moderation={moderation} style={styles.alert} />
) : null}
{!isEmpty ? ( {!isEmpty ? (
<Text type="post-text" style={pal.text} numberOfLines={6}> <Text type="post-text" style={pal.text} numberOfLines={6}>
{quote.text} {quote.text}
</Text> </Text>
) : null} ) : null}
{AppBskyEmbedImages.isView(imagesEmbed) && ( {AppBskyEmbedImages.isView(imagesEmbed) && (
<PostEmbeds embed={imagesEmbed} /> <PostEmbeds embed={imagesEmbed} moderation={{}} />
)} )}
{AppBskyEmbedRecordWithMedia.isView(imagesEmbed) && ( {AppBskyEmbedRecordWithMedia.isView(imagesEmbed) && (
<PostEmbeds embed={imagesEmbed.media} /> <PostEmbeds embed={imagesEmbed.media} moderation={{}} />
)} )}
</Link> </Link>
) )
@ -140,4 +150,7 @@ const styles = StyleSheet.create({
paddingHorizontal: 14, paddingHorizontal: 14,
borderWidth: 1, borderWidth: 1,
}, },
alert: {
marginBottom: 6,
},
}) })

View File

@ -14,6 +14,7 @@ import {
AppBskyEmbedRecordWithMedia, AppBskyEmbedRecordWithMedia,
AppBskyFeedDefs, AppBskyFeedDefs,
AppBskyGraphDefs, AppBskyGraphDefs,
ModerationUI,
} from '@atproto/api' } from '@atproto/api'
import {Link} from '../Link' import {Link} from '../Link'
import {ImageLayoutGrid} from '../images/ImageLayoutGrid' import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
@ -28,6 +29,7 @@ import {AutoSizedImage} from '../images/AutoSizedImage'
import {CustomFeedEmbed} from './CustomFeedEmbed' import {CustomFeedEmbed} from './CustomFeedEmbed'
import {ListEmbed} from './ListEmbed' import {ListEmbed} from './ListEmbed'
import {isDesktopWeb} from 'platform/detection' import {isDesktopWeb} from 'platform/detection'
import {isCauseALabelOnUri} from 'lib/moderation'
type Embed = type Embed =
| AppBskyEmbedRecord.View | AppBskyEmbedRecord.View
@ -38,9 +40,11 @@ type Embed =
export function PostEmbeds({ export function PostEmbeds({
embed, embed,
moderation,
style, style,
}: { }: {
embed?: Embed embed?: Embed
moderation: ModerationUI
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
@ -49,10 +53,15 @@ export function PostEmbeds({
// quote post with media // quote post with media
// = // =
if (AppBskyEmbedRecordWithMedia.isView(embed)) { if (AppBskyEmbedRecordWithMedia.isView(embed)) {
const isModOnQuote =
AppBskyEmbedRecord.isViewRecord(embed.record.record) &&
isCauseALabelOnUri(moderation.cause, embed.record.record.uri)
const mediaModeration = isModOnQuote ? {} : moderation
const quoteModeration = isModOnQuote ? moderation : {}
return ( return (
<View style={[styles.stackContainer, style]}> <View style={[styles.stackContainer, style]}>
<PostEmbeds embed={embed.media} /> <PostEmbeds embed={embed.media} moderation={mediaModeration} />
<MaybeQuoteEmbed embed={embed.record} /> <MaybeQuoteEmbed embed={embed.record} moderation={quoteModeration} />
</View> </View>
) )
} }
@ -71,7 +80,9 @@ export function PostEmbeds({
// quote post // quote post
// = // =
return <MaybeQuoteEmbed embed={embed} style={style} /> return (
<MaybeQuoteEmbed embed={embed} style={style} moderation={moderation} />
)
} }
// image embed // image embed