diff options
author | Hailey <me@haileyok.com> | 2024-04-13 03:18:18 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-13 11:18:18 +0100 |
commit | 826f6b043ca73f3cc459fbac62ae6de5f82e362b (patch) | |
tree | 296cd847fa76d2d0691ffdeaa59b6731714cef47 /bskyembed | |
parent | f5bb348bf51df6f6d35eb23cdf771c184d77fec4 (diff) | |
download | voidsky-826f6b043ca73f3cc459fbac62ae6de5f82e362b.tar.zst |
Moderate content in embeds (#3525)
* move info to its own file * Revert "move info to its own file" This reverts commit 1d45a2f4034f50cbe9cb25070f954042cdf9127a. * better way * all cases * pass labelInfo to ImageEmbed * blur avatars * add back as string * one more as string * external embed * add back as string again
Diffstat (limited to 'bskyembed')
-rw-r--r-- | bskyembed/src/components/embed.tsx | 70 | ||||
-rw-r--r-- | bskyembed/src/components/post.tsx | 17 | ||||
-rw-r--r-- | bskyembed/src/labels.ts | 21 |
3 files changed, 91 insertions, 17 deletions
diff --git a/bskyembed/src/components/embed.tsx b/bskyembed/src/components/embed.tsx index 2f9f6b3cd..d88019965 100644 --- a/bskyembed/src/components/embed.tsx +++ b/bskyembed/src/components/embed.tsx @@ -9,23 +9,33 @@ import { AppBskyLabelerDefs, } from '@atproto/api' import {ComponentChildren, h} from 'preact' +import {useMemo} from 'preact/hooks' import infoIcon from '../../assets/circleInfo_stroke2_corner0_rounded.svg' +import {CONTENT_LABELS, labelsToInfo} from '../labels' import {getRkey} from '../utils' import {Link} from './link' -export function Embed({content}: {content: AppBskyFeedDefs.PostView['embed']}) { +export function Embed({ + content, + labels, +}: { + content: AppBskyFeedDefs.PostView['embed'] + labels: AppBskyFeedDefs.PostView['labels'] +}) { + const labelInfo = useMemo(() => labelsToInfo(labels), [labels]) + if (!content) return null try { // Case 1: Image if (AppBskyEmbedImages.isView(content)) { - return <ImageEmbed content={content} /> + return <ImageEmbed content={content} labelInfo={labelInfo} /> } // Case 2: External link if (AppBskyEmbedExternal.isView(content)) { - return <ExternalEmbed content={content} /> + return <ExternalEmbed content={content} labelInfo={labelInfo} /> } // Case 3: Record (quote or linked post) @@ -50,15 +60,22 @@ export function Embed({content}: {content: AppBskyFeedDefs.PostView['embed']}) { if (AppBskyFeedPost.isRecord(record.value)) { text = record.value.text } + + const isAuthorLabeled = record.author.labels?.some(label => + CONTENT_LABELS.includes(label.val), + ) + return ( <Link href={`/profile/${record.author.did}/post/${getRkey(record)}`} className="transition-colors hover:bg-neutral-100 border rounded-lg p-2 gap-1.5 w-full flex flex-col"> <div className="flex gap-1.5 items-center"> - <img - src={record.author.avatar} - className="w-4 h-4 rounded-full bg-neutral-300 shrink-0" - /> + <div className="w-4 h-4 overflow-hidden rounded-full bg-neutral-300 shrink-0"> + <img + src={record.author.avatar} + style={isAuthorLabeled ? {filter: 'blur(1.5px)'} : undefined} + /> + </div> <p className="line-clamp-1 text-sm"> <span className="font-bold">{record.author.displayName}</span> <span className="text-textLight ml-1"> @@ -74,7 +91,11 @@ export function Embed({content}: {content: AppBskyFeedDefs.PostView['embed']}) { return false }) .map(embed => ( - <Embed key={embed.$type} content={embed} /> + <Embed + key={embed.$type} + content={embed} + labels={record.labels} + /> ))} </Link> ) @@ -137,15 +158,19 @@ export function Embed({content}: {content: AppBskyFeedDefs.PostView['embed']}) { } // Case 4: Record with media - if (AppBskyEmbedRecordWithMedia.isView(content)) { + if ( + AppBskyEmbedRecordWithMedia.isView(content) && + AppBskyEmbedRecord.isViewRecord(content.record.record) + ) { return ( <div className="flex flex-col gap-2"> - <Embed content={content.media} /> + <Embed content={content.media} labels={labels} /> <Embed content={{ $type: 'app.bsky.embed.record#view', record: content.record.record, }} + labels={content.record.record.labels} /> </div> ) @@ -168,7 +193,17 @@ function Info({children}: {children: ComponentChildren}) { ) } -function ImageEmbed({content}: {content: AppBskyEmbedImages.View}) { +function ImageEmbed({ + content, + labelInfo, +}: { + content: AppBskyEmbedImages.View + labelInfo?: string +}) { + if (labelInfo) { + return <Info>{labelInfo}</Info> + } + switch (content.images.length) { case 1: return ( @@ -229,7 +264,13 @@ function ImageEmbed({content}: {content: AppBskyEmbedImages.View}) { } } -function ExternalEmbed({content}: {content: AppBskyEmbedExternal.View}) { +function ExternalEmbed({ + content, + labelInfo, +}: { + content: AppBskyEmbedExternal.View + labelInfo?: string +}) { function toNiceDomain(url: string): string { try { const urlp = new URL(url) @@ -238,6 +279,11 @@ function ExternalEmbed({content}: {content: AppBskyEmbedExternal.View}) { return url } } + + if (labelInfo) { + return <Info>{labelInfo}</Info> + } + return ( <Link href={content.external.uri} diff --git a/bskyembed/src/components/post.tsx b/bskyembed/src/components/post.tsx index dcbf3e336..d0eaf228a 100644 --- a/bskyembed/src/components/post.tsx +++ b/bskyembed/src/components/post.tsx @@ -5,6 +5,7 @@ import replyIcon from '../../assets/bubble_filled_stroke2_corner2_rounded.svg' import likeIcon from '../../assets/heart2_filled_stroke2_corner0_rounded.svg' import logo from '../../assets/logo.svg' import repostIcon from '../../assets/repost_stroke2_corner2_rounded.svg' +import {CONTENT_LABELS} from '../labels' import {getRkey, niceDate} from '../utils' import {Container} from './container' import {Embed} from './embed' @@ -17,6 +18,10 @@ interface Props { export function Post({thread}: Props) { const post = thread.post + const isAuthorLabeled = post.author.labels?.some(label => + CONTENT_LABELS.includes(label.val), + ) + let record: AppBskyFeedPost.Record | null = null if (AppBskyFeedPost.isRecord(post.record)) { record = post.record @@ -28,10 +33,12 @@ export function Post({thread}: Props) { <div className="flex-1 flex-col flex gap-2" lang={record?.langs?.[0]}> <div className="flex gap-2.5 items-center"> <Link href={`/profile/${post.author.did}`} className="rounded-full"> - <img - src={post.author.avatar} - className="w-10 h-10 rounded-full bg-neutral-300 shrink-0" - /> + <div className="w-10 h-10 overflow-hidden rounded-full bg-neutral-300 shrink-0"> + <img + src={post.author.avatar} + style={isAuthorLabeled ? {filter: 'blur(2.5px)'} : undefined} + /> + </div> </Link> <div className="flex-1"> <Link @@ -52,7 +59,7 @@ export function Post({thread}: Props) { </Link> </div> <PostContent record={record} /> - <Embed content={post.embed} /> + <Embed content={post.embed} labels={post.labels} /> <time datetime={new Date(post.indexedAt).toISOString()} className="text-textLight mt-1 text-sm"> diff --git a/bskyembed/src/labels.ts b/bskyembed/src/labels.ts new file mode 100644 index 000000000..ff3d91bc7 --- /dev/null +++ b/bskyembed/src/labels.ts @@ -0,0 +1,21 @@ +import {AppBskyFeedDefs} from '@atproto/api' + +export const CONTENT_LABELS = ['porn', 'sexual', 'nudity', 'graphic-media'] + +export function labelsToInfo( + labels?: AppBskyFeedDefs.PostView['labels'], +): string | undefined { + const label = labels?.find(label => CONTENT_LABELS.includes(label.val)) + + switch (label?.val) { + case 'porn': + case 'sexual': + return 'Adult Content' + case 'nudity': + return 'Non-sexual Nudity' + case 'graphic-media': + return 'Graphic Media' + default: + return undefined + } +} |