mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

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

at samuel/fancy-queue 178 lines 5.8 kB view raw
1import { 2 AppBskyFeedDefs, 3 AppBskyFeedPost, 4 AppBskyRichtextFacet, 5 RichText, 6} from '@atproto/api' 7import {h} from 'preact' 8 9import replyIcon from '../../assets/bubble_filled_stroke2_corner2_rounded.svg' 10import likeIcon from '../../assets/heart2_filled_stroke2_corner0_rounded.svg' 11import logo from '../../assets/logo.svg' 12import repostIcon from '../../assets/repost_stroke2_corner2_rounded.svg' 13import {CONTENT_LABELS} from '../labels' 14import {getRkey, niceDate, prettyNumber} from '../utils' 15import {Container} from './container' 16import {Embed} from './embed' 17import {Link} from './link' 18 19interface Props { 20 thread: AppBskyFeedDefs.ThreadViewPost 21} 22 23export function Post({thread}: Props) { 24 const post = thread.post 25 26 const isAuthorLabeled = post.author.labels?.some(label => 27 CONTENT_LABELS.includes(label.val), 28 ) 29 30 let record: AppBskyFeedPost.Record | null = null 31 if (AppBskyFeedPost.isRecord(post.record)) { 32 record = post.record 33 } 34 35 const href = `/profile/${post.author.did}/post/${getRkey(post)}` 36 return ( 37 <Container href={href}> 38 <div className="flex-1 flex-col flex gap-2" lang={record?.langs?.[0]}> 39 <div className="flex gap-2.5 items-center cursor-pointer"> 40 <Link href={`/profile/${post.author.did}`} className="rounded-full"> 41 <div className="w-10 h-10 overflow-hidden rounded-full bg-neutral-300 shrink-0"> 42 <img 43 src={post.author.avatar} 44 style={isAuthorLabeled ? {filter: 'blur(2.5px)'} : undefined} 45 /> 46 </div> 47 </Link> 48 <div> 49 <Link 50 href={`/profile/${post.author.did}`} 51 className="font-bold text-[17px] leading-5 line-clamp-1 hover:underline underline-offset-2 decoration-2"> 52 <p>{post.author.displayName}</p> 53 </Link> 54 <Link 55 href={`/profile/${post.author.did}`} 56 className="text-[15px] text-textLight hover:underline line-clamp-1"> 57 <p>@{post.author.handle}</p> 58 </Link> 59 </div> 60 <div className="flex-1" /> 61 <Link 62 href={href} 63 className="transition-transform hover:scale-110 shrink-0 self-start"> 64 <img src={logo} className="h-8" /> 65 </Link> 66 </div> 67 <PostContent record={record} /> 68 <Embed content={post.embed} labels={post.labels} /> 69 <Link href={href}> 70 <time 71 datetime={new Date(post.indexedAt).toISOString()} 72 className="text-textLight mt-1 text-sm hover:underline"> 73 {niceDate(post.indexedAt)} 74 </time> 75 </Link> 76 <div className="border-t w-full pt-2.5 flex items-center gap-5 text-sm cursor-pointer"> 77 {!!post.likeCount && ( 78 <div className="flex items-center gap-2 cursor-pointer"> 79 <img src={likeIcon} className="w-5 h-5" /> 80 <p className="font-bold text-neutral-500 mb-px"> 81 {prettyNumber(post.likeCount)} 82 </p> 83 </div> 84 )} 85 {!!post.repostCount && ( 86 <div className="flex items-center gap-2 cursor-pointer"> 87 <img src={repostIcon} className="w-5 h-5" /> 88 <p className="font-bold text-neutral-500 mb-px"> 89 {prettyNumber(post.repostCount)} 90 </p> 91 </div> 92 )} 93 <div className="flex items-center gap-2 cursor-pointer"> 94 <img src={replyIcon} className="w-5 h-5" /> 95 <p className="font-bold text-neutral-500 mb-px">Reply</p> 96 </div> 97 <div className="flex-1" /> 98 <p className="cursor-pointer text-brand font-bold hover:underline hidden min-[450px]:inline"> 99 {post.replyCount 100 ? `Read ${prettyNumber(post.replyCount)} ${ 101 post.replyCount > 1 ? 'replies' : 'reply' 102 } on Bluesky` 103 : `View on Bluesky`} 104 </p> 105 <p className="cursor-pointer text-brand font-bold hover:underline min-[450px]:hidden"> 106 <span className="hidden min-[380px]:inline">View on </span>Bluesky 107 </p> 108 </div> 109 </div> 110 </Container> 111 ) 112} 113 114function PostContent({record}: {record: AppBskyFeedPost.Record | null}) { 115 if (!record) return null 116 117 const rt = new RichText({ 118 text: record.text, 119 facets: record.facets, 120 }) 121 122 const richText = [] 123 124 let counter = 0 125 for (const segment of rt.segments()) { 126 if ( 127 segment.link && 128 AppBskyRichtextFacet.validateLink(segment.link).success 129 ) { 130 richText.push( 131 <Link 132 key={counter} 133 href={segment.link.uri} 134 className="text-blue-400 hover:underline" 135 disableTracking={ 136 !segment.link.uri.startsWith('https://bsky.app') && 137 !segment.link.uri.startsWith('https://go.bsky.app') 138 }> 139 {segment.text} 140 </Link>, 141 ) 142 } else if ( 143 segment.mention && 144 AppBskyRichtextFacet.validateMention(segment.mention).success 145 ) { 146 richText.push( 147 <Link 148 key={counter} 149 href={`/profile/${segment.mention.did}`} 150 className="text-blue-500 hover:underline"> 151 {segment.text} 152 </Link>, 153 ) 154 } else if ( 155 segment.tag && 156 AppBskyRichtextFacet.validateTag(segment.tag).success 157 ) { 158 richText.push( 159 <Link 160 key={counter} 161 href={`/tag/${segment.tag.tag}`} 162 className="text-blue-500 hover:underline"> 163 {segment.text} 164 </Link>, 165 ) 166 } else { 167 richText.push(segment.text) 168 } 169 170 counter++ 171 } 172 173 return ( 174 <p className="min-[300px]:text-lg leading-6 break-word break-words whitespace-pre-wrap"> 175 {richText} 176 </p> 177 ) 178}