This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

semble / src / webapp / features / platforms / bluesky / components / blueskySemblePost / BlueskySemblePost.tsx
4.1 kB 113 lines
1import { AppBskyFeedDefs, AppBskyFeedPost } from '@atproto/api'; 2import { getBlueskyPost } from '../../lib/dal'; 3import SembleHeader from '@/features/semble/components/SembleHeader/SembleHeader'; 4import { getPostUriFromUrl } from '@/lib/utils/atproto'; 5import RichTextRenderer from '@/components/contentDisplay/richTextRenderer/RichTextRenderer'; 6import { detectUrlPlatform, SupportedPlatform } from '@/lib/utils/link'; 7import { getFormattedDate } from '@/lib/utils/time'; 8import { Stack, Tooltip, Anchor, Card, Group, Box, Text } from '@mantine/core'; 9import BlueskyPlatformIcon from '../blueskyPlatformIcon/BlueskyPlatformIcon'; 10import PostEmbed from '../postEmbed/PostEmbed'; 11import ContentHider from '../contentHider/ContentHider'; 12import { 13 getPostModerationUI, 14 getModerationReasonText, 15} from '../../lib/moderation'; 16 17import { Suspense } from 'react'; 18import UrlTypeBadge from '@/features/semble/components/urlTypeBadge/UrlTypeBadge'; 19import UrlTypeBadgeSkeleton from '@/features/semble/components/urlTypeBadge/Skeleton.UrlTypeBadge'; 20import { isBotAccount } from '../../lib/utils/account'; 21import BotLabel from '@/features/profile/components/botLabel/BotLabel'; 22import { LinkAvatar } from '@/components/link/MantineLink'; 23 24interface Props { 25 url: string; 26} 27 28export default async function BlueskySemblePost(props: Props) { 29 const postUri = getPostUriFromUrl(props.url); 30 const data = await getBlueskyPost(postUri); 31 32 const platform = detectUrlPlatform(props.url); 33 const platformIcon = <BlueskyPlatformIcon platform={platform.type} />; 34 35 if ( 36 !data.thread || 37 !AppBskyFeedDefs.isThreadViewPost(data.thread) || 38 AppBskyFeedDefs.isNotFoundPost(data.thread.post) || 39 AppBskyFeedDefs.isBlockedPost(data.thread.post) 40 ) { 41 // fallback 42 return <SembleHeader url={props.url} />; 43 } 44 45 const post = data.thread.post; 46 const record = post.record as AppBskyFeedPost.Record; 47 const moderation = getPostModerationUI(post); 48 49 if (moderation.filter) { 50 return <SembleHeader url={props.url} />; 51 } 52 53 return ( 54 <Stack gap={'xs'}> 55 <Suspense fallback={<UrlTypeBadgeSkeleton />}> 56 <UrlTypeBadge url={props.url} /> 57 </Suspense> 58 59 {/* Post */} 60 <Card p={'xs'} radius={'lg'} withBorder> 61 <Stack gap={'xs'}> 62 <Group gap="xs" justify="space-between" wrap="nowrap"> 63 <Group gap={'xs'} wrap="nowrap"> 64 <LinkAvatar 65 href={`https://bsky.app/profile/${post.author.handle}`} 66 src={post.author.avatar?.replace('avatar', 'avatar_thumbnail')} 67 alt={`${post.author.handle} social preview image`} 68 radius="xl" 69 /> 70 <Stack gap={0} flex={1}> 71 <Group gap={'xs'}> 72 <Text c="bright" lineClamp={1} fw={500} w="fit-content"> 73 {post.author.displayName || post.author.handle} 74 </Text> 75 {isBotAccount(post.author) && <BotLabel />} 76 </Group> 77 78 <Text c="gray" lineClamp={1} w="fit-content"> 79 @{post.author.handle} 80 </Text> 81 </Stack> 82 </Group> 83 <Tooltip 84 label={`View on ${platform.type === SupportedPlatform.BLUESKY_POST ? 'Bluesky' : 'Blacksky'}`} 85 > 86 <Anchor href={props.url} target="_blank"> 87 {platformIcon} 88 </Anchor> 89 </Tooltip> 90 </Group> 91 <ContentHider 92 blur={moderation.blur} 93 noOverride={moderation.noOverride} 94 reason={getModerationReasonText(moderation)} 95 > 96 <Stack gap={'xs'} w={'100%'}> 97 <Box> 98 <RichTextRenderer 99 text={record.text} 100 textProps={{ c: 'bright' }} 101 /> 102 </Box> 103 {post.embed && <PostEmbed embed={post.embed} />} 104 </Stack> 105 </ContentHider> 106 <Text c={'gray'} fz={'sm'} fw={500}> 107 {getFormattedDate(post.indexedAt)} 108 </Text> 109 </Stack> 110 </Card> 111 </Stack> 112 ); 113}