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
3.9 kB 123 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, getDomain, SupportedPlatform } from '@/lib/utils/link'; 7import { getFormattedDate } from '@/lib/utils/time'; 8import { 9 Stack, 10 Tooltip, 11 Anchor, 12 Card, 13 Group, 14 Avatar, 15 Box, 16 Image, 17 Text, 18} from '@mantine/core'; 19import Link from 'next/link'; 20import { FaBluesky } from 'react-icons/fa6'; 21import PostEmbed from '../postEmbed/PostEmbed'; 22import BlackskyLogo from '@/assets/icons/blacksky-logo.svg'; 23import BlackskyLogoWhite from '@/assets/icons/blacksky-logo-white.svg'; 24import { Suspense } from 'react'; 25import UrlTypeBadge from '@/features/semble/components/urlTypeBadge/UrlTypeBadge'; 26import UrlTypeBadgeSkeleton from '@/features/semble/components/urlTypeBadge/Skeleton.UrlTypeBadge'; 27 28interface Props { 29 url: string; 30} 31 32export default async function BlueskySemblePost(props: Props) { 33 const postUri = getPostUriFromUrl(props.url); 34 const data = await getBlueskyPost(postUri); 35 36 const platform = detectUrlPlatform(props.url); 37 const platformIcon = 38 platform.type === SupportedPlatform.BLUESKY_POST ? ( 39 <FaBluesky fill="#0085ff" size={18} /> 40 ) : ( 41 <> 42 <Image 43 src={BlackskyLogo.src} 44 alt="Blacksky logo" 45 w={18} 46 h={'auto'} 47 darkHidden 48 /> 49 <Image 50 src={BlackskyLogoWhite.src} 51 alt="Blacksky logo" 52 w={18} 53 h={'auto'} 54 lightHidden 55 /> 56 </> 57 ); 58 59 if ( 60 !data.thread || 61 !AppBskyFeedDefs.isThreadViewPost(data.thread) || 62 AppBskyFeedDefs.isNotFoundPost(data.thread.post) || 63 AppBskyFeedDefs.isBlockedPost(data.thread.post) 64 ) { 65 // fallback 66 return <SembleHeader url={props.url} />; 67 } 68 69 const post = data.thread.post; 70 const record = post.record as AppBskyFeedPost.Record; 71 72 return ( 73 <Stack gap={'xs'}> 74 <Suspense fallback={<UrlTypeBadgeSkeleton />}> 75 <UrlTypeBadge url={props.url} /> 76 </Suspense> 77 78 {/* Post */} 79 <Card radius={'lg'} withBorder> 80 <Stack gap={'xs'}> 81 <Group gap="xs" justify="space-between" wrap="nowrap"> 82 <Group gap={'xs'} wrap="nowrap"> 83 <Avatar 84 component={Link} 85 href={`https://bsky.app/profile/${post.author.handle}`} 86 src={post.author.avatar?.replace('avatar', 'avatar_thumbnail')} 87 alt={`${post.author.handle} social preview image`} 88 radius="xl" 89 /> 90 <Stack gap={0} flex={1}> 91 <Text c="bright" lineClamp={1} fw={500} w="fit-content"> 92 {post.author.displayName || post.author.handle} 93 </Text> 94 <Text c="gray" lineClamp={1} w="fit-content"> 95 @{post.author.handle} 96 </Text> 97 </Stack> 98 </Group> 99 <Tooltip 100 label={`View on ${platform.type === SupportedPlatform.BLUESKY_POST ? 'Bluesky' : 'Blacksky'}`} 101 > 102 <Anchor href={props.url} target="_blank"> 103 {platformIcon} 104 </Anchor> 105 </Tooltip> 106 </Group> 107 <Stack gap={'xs'} w={'100%'}> 108 <Box> 109 <RichTextRenderer 110 text={record.text} 111 textProps={{ c: 'bright' }} 112 /> 113 </Box> 114 {post.embed && <PostEmbed embed={post.embed} />} 115 </Stack> 116 <Text c={'gray'} fz={'sm'} fw={500}> 117 {getFormattedDate(post.indexedAt)} 118 </Text> 119 </Stack> 120 </Card> 121 </Stack> 122 ); 123}