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