diff options
author | Samuel Newman <mozzius@protonmail.com> | 2025-08-26 18:24:46 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-08-26 08:24:46 -0700 |
commit | b70e5b2f387e8de6dac5d388aee1ccbf5b217adc (patch) | |
tree | e540d731ec004e58e1280c6382b00b6947d03e63 /bskyembed/src/components | |
parent | 5a074fa37acafb0cf11acbdd0a931411b1c63aa2 (diff) | |
download | voidsky-b70e5b2f387e8de6dac5d388aee1ccbf5b217adc.tar.zst |
Add verification checkmarks to `embed.bsky.app` (#8644)
* update vite+typescript * update atproto api to latest, split out utils * add checkmark to post * add checkie to embed * revert change to example post * fix ext link color
Diffstat (limited to 'bskyembed/src/components')
-rw-r--r-- | bskyembed/src/components/container.tsx | 2 | ||||
-rw-r--r-- | bskyembed/src/components/embed.tsx | 40 | ||||
-rw-r--r-- | bskyembed/src/components/post.tsx | 50 | ||||
-rw-r--r-- | bskyembed/src/components/verification-check.tsx | 56 |
4 files changed, 124 insertions, 24 deletions
diff --git a/bskyembed/src/components/container.tsx b/bskyembed/src/components/container.tsx index 8e142a25b..bafc497ae 100644 --- a/bskyembed/src/components/container.tsx +++ b/bskyembed/src/components/container.tsx @@ -49,7 +49,7 @@ export function Container({ } }}> {href && <Link href={href} />} - <div className="flex-1 px-4 pt-3 pb-2.5">{children}</div> + <div className="flex-1 px-4 pt-3 pb-2.5 max-w-full">{children}</div> </div> ) } diff --git a/bskyembed/src/components/embed.tsx b/bskyembed/src/components/embed.tsx index 428782b64..52618a89d 100644 --- a/bskyembed/src/components/embed.tsx +++ b/bskyembed/src/components/embed.tsx @@ -17,8 +17,11 @@ import infoIcon from '../../assets/circleInfo_stroke2_corner0_rounded.svg' import playIcon from '../../assets/play_filled_corner2_rounded.svg' import starterPackIcon from '../../assets/starterPack.svg' import {CONTENT_LABELS, labelsToInfo} from '../labels' -import {getRkey} from '../utils' +import * as bsky from '../types/bsky' +import {getRkey} from '../util/rkey' +import {getVerificationState} from '../util/verification-state' import {Link} from './link' +import {VerificationCheck} from './verification-check' export function Embed({ content, @@ -75,23 +78,35 @@ export function Embed({ CONTENT_LABELS.includes(label.val), ) + const verification = getVerificationState({profile: record.author}) + return ( <Link href={`/profile/${record.author.did}/post/${getRkey(record)}`} className="transition-colors hover:bg-neutral-100 dark:hover:bg-slate-700 border dark:border-slate-600 rounded-xl p-2 gap-1.5 w-full flex flex-col"> <div className="flex gap-1.5 items-center"> - <div className="w-4 h-4 overflow-hidden rounded-full bg-neutral-300 dark:bg-slate-700 shrink-0"> + <div className="w-4 h-4 rounded-full bg-neutral-300 dark:bg-slate-700 shrink-0"> <img + className="rounded-full" src={record.author.avatar} style={isAuthorLabeled ? {filter: 'blur(1.5px)'} : undefined} /> </div> - <p className="line-clamp-1 text-sm"> - <span className="font-bold">{record.author.displayName}</span> - <span className="text-textLight dark:text-textDimmed ml-1"> + <div className="flex flex-1 items-center shrink min-w-0 min-h-0"> + <p className="block text-sm shrink-0 font-bold max-w-[70%] line-clamp-1"> + {record.author.displayName?.trim() || record.author.handle} + </p> + {verification.isVerified && ( + <VerificationCheck + className="ml-[3px] mt-px shrink-0 self-center" + verifier={verification.role === 'verifier'} + size={12} + /> + )} + <p className="block line-clamp-1 text-sm text-textLight dark:text-textDimmed shrink-[10] ml-1"> @{record.author.handle} - </span> - </p> + </p> + </div> </div> {text && <p className="text-sm">{text}</p>} {record.embeds?.map(embed => ( @@ -404,7 +419,12 @@ function StarterPackEmbed({ }: { content: AppBskyGraphDefs.StarterPackViewBasic }) { - if (!AppBskyGraphStarterpack.isRecord(content.record)) { + if ( + !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( + content.record, + AppBskyGraphStarterpack.isRecord, + ) + ) { return null } @@ -443,7 +463,9 @@ function StarterPackEmbed({ } // from #/lib/strings/starter-pack.ts -function getStarterPackImage(starterPack: AppBskyGraphDefs.StarterPackView) { +function getStarterPackImage( + starterPack: AppBskyGraphDefs.StarterPackViewBasic, +) { const rkey = getRkey({uri: starterPack.uri}) return `https://ogcard.cdn.bsky.app/start/${starterPack.creator.did}/${rkey}` } diff --git a/bskyembed/src/components/post.tsx b/bskyembed/src/components/post.tsx index 6ecac5796..d216ce0e5 100644 --- a/bskyembed/src/components/post.tsx +++ b/bskyembed/src/components/post.tsx @@ -11,10 +11,15 @@ import likeIcon from '../../assets/heart2_filled_stroke2_corner0_rounded.svg' import logo from '../../assets/logo.svg' import repostIcon from '../../assets/repost_stroke2_corner2_rounded.svg' import {CONTENT_LABELS} from '../labels' -import {getRkey, niceDate, prettyNumber} from '../utils' +import * as bsky from '../types/bsky' +import {niceDate} from '../util/nice-date' +import {prettyNumber} from '../util/pretty-number' +import {getRkey} from '../util/rkey' +import {getVerificationState} from '../util/verification-state' import {Container} from './container' import {Embed} from './embed' import {Link} from './link' +import {VerificationCheck} from './verification-check' interface Props { thread: AppBskyFeedDefs.ThreadViewPost @@ -28,16 +33,25 @@ export function Post({thread}: Props) { ) let record: AppBskyFeedPost.Record | null = null - if (AppBskyFeedPost.isRecord(post.record)) { + if ( + bsky.dangerousIsType<AppBskyFeedPost.Record>( + post.record, + AppBskyFeedPost.isRecord, + ) + ) { record = post.record } + const verification = getVerificationState({profile: post.author}) + const href = `/profile/${post.author.did}/post/${getRkey(post)}` return ( <Container href={href}> <div className="flex-1 flex-col flex gap-2" lang={record?.langs?.[0]}> - <div className="flex gap-2.5 items-center cursor-pointer"> - <Link href={`/profile/${post.author.did}`} className="rounded-full"> + <div className="flex gap-2.5 items-center cursor-pointer w-full max-w-full"> + <Link + href={`/profile/${post.author.did}`} + className="rounded-full shrink-0"> <div className="w-10 h-10 overflow-hidden rounded-full bg-neutral-300 dark:bg-slate-700 shrink-0"> <img src={post.author.avatar} @@ -45,19 +59,27 @@ export function Post({thread}: Props) { /> </div> </Link> - <div> - <Link - href={`/profile/${post.author.did}`} - className="font-bold text-[17px] leading-5 line-clamp-1 hover:underline underline-offset-2 decoration-2"> - <p>{post.author.displayName}</p> - </Link> + <div className="flex flex-1 flex-col min-w-0"> + <div className="flex flex-1 items-center"> + <Link + href={`/profile/${post.author.did}`} + className="block font-bold text-[17px] leading-5 line-clamp-1 hover:underline underline-offset-2 text-ellipsis decoration-2"> + {post.author.displayName?.trim() || post.author.handle} + </Link> + {verification.isVerified && ( + <VerificationCheck + className="pl-[3px] mt-px shrink-0" + verifier={verification.role === 'verifier'} + size={15} + /> + )} + </div> <Link href={`/profile/${post.author.did}`} - className="text-[15px] text-textLight dark:text-textDimmed hover:underline line-clamp-1"> - <p>@{post.author.handle}</p> + className="block text-[15px] text-textLight dark:text-textDimmed hover:underline line-clamp-1"> + @{post.author.handle} </Link> </div> - <div className="flex-1" /> <Link href={href} className="transition-transform hover:scale-110 shrink-0 self-start"> @@ -133,7 +155,7 @@ function PostContent({record}: {record: AppBskyFeedPost.Record | null}) { <Link key={counter} href={segment.link.uri} - className="text-blue-400 hover:underline" + className="text-blue-500 hover:underline" disableTracking={ !segment.link.uri.startsWith('https://bsky.app') && !segment.link.uri.startsWith('https://go.bsky.app') diff --git a/bskyembed/src/components/verification-check.tsx b/bskyembed/src/components/verification-check.tsx new file mode 100644 index 000000000..a9710ea2f --- /dev/null +++ b/bskyembed/src/components/verification-check.tsx @@ -0,0 +1,56 @@ +import {h} from 'preact' + +type IconProps = { + size: number + className?: string +} + +export function VerificationCheck({ + verifier, + ...rest +}: {verifier: boolean} & IconProps) { + return verifier ? <VerifierCheck {...rest} /> : <VerifiedCheck {...rest} /> +} + +export function VerifiedCheck({size, className}: IconProps) { + return ( + <svg + fill="none" + viewBox="0 0 24 24" + width={size} + height={size} + className={className}> + <circle cx="12" cy="12" r="11.5" fill="hsl(211, 99%, 53%)" /> + <path + fill="#fff" + fillRule="evenodd" + clipRule="evenodd" + d="M17.659 8.175a1.361 1.361 0 0 1 0 1.925l-6.224 6.223a1.361 1.361 0 0 1-1.925 0L6.4 13.212a1.361 1.361 0 0 1 1.925-1.925l2.149 2.148 5.26-5.26a1.361 1.361 0 0 1 1.925 0Z" + /> + </svg> + ) +} + +export function VerifierCheck({size, className}: IconProps) { + return ( + <svg + fill="none" + viewBox="0 0 24 24" + width={size} + height={size} + className={className}> + <path + fill="hsl(211, 99%, 53%)" + fillRule="evenodd" + clipRule="evenodd" + d="M8.792 1.615a4.154 4.154 0 0 1 6.416 0 4.154 4.154 0 0 0 3.146 1.515 4.154 4.154 0 0 1 4 5.017 4.154 4.154 0 0 0 .777 3.404 4.154 4.154 0 0 1-1.427 6.255 4.153 4.153 0 0 0-2.177 2.73 4.154 4.154 0 0 1-5.781 2.784 4.154 4.154 0 0 0-3.492 0 4.154 4.154 0 0 1-5.78-2.784 4.154 4.154 0 0 0-2.178-2.73A4.154 4.154 0 0 1 .87 11.551a4.154 4.154 0 0 0 .776-3.404A4.154 4.154 0 0 1 5.646 3.13a4.154 4.154 0 0 0 3.146-1.515Z" + /> + <path + fill="#fff" + fillRule="evenodd" + clipRule="evenodd" + d="M17.861 8.26a1.438 1.438 0 0 1 0 2.033l-6.571 6.571a1.437 1.437 0 0 1-2.033 0L5.97 13.58a1.438 1.438 0 0 1 2.033-2.033l2.27 2.269 5.554-5.555a1.437 1.437 0 0 1 2.033 0Z" + /> + </svg> + ) +} |