This repository has no description
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}