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/util | |
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/util')
-rw-r--r-- | bskyembed/src/util/nice-date.ts | 11 | ||||
-rw-r--r-- | bskyembed/src/util/parse-embed.ts | 152 | ||||
-rw-r--r-- | bskyembed/src/util/pretty-number.ts | 9 | ||||
-rw-r--r-- | bskyembed/src/util/rkey.ts | 6 | ||||
-rw-r--r-- | bskyembed/src/util/verification-state.ts | 31 |
5 files changed, 209 insertions, 0 deletions
diff --git a/bskyembed/src/util/nice-date.ts b/bskyembed/src/util/nice-date.ts new file mode 100644 index 000000000..016c97a69 --- /dev/null +++ b/bskyembed/src/util/nice-date.ts @@ -0,0 +1,11 @@ +export function niceDate(date: number | string | Date) { + const d = new Date(date) + return `${d.toLocaleDateString('en-us', { + year: 'numeric', + month: 'short', + day: 'numeric', + })} at ${d.toLocaleTimeString(undefined, { + hour: 'numeric', + minute: '2-digit', + })}` +} diff --git a/bskyembed/src/util/parse-embed.ts b/bskyembed/src/util/parse-embed.ts new file mode 100644 index 000000000..97c3dc33e --- /dev/null +++ b/bskyembed/src/util/parse-embed.ts @@ -0,0 +1,152 @@ +/** + * This file is a copy of what exists in the social-app + */ + +import { + AppBskyEmbedExternal, + AppBskyEmbedImages, + AppBskyEmbedRecord, + AppBskyEmbedRecordWithMedia, + AppBskyEmbedVideo, + AppBskyFeedDefs, + AppBskyGraphDefs, + AppBskyLabelerDefs, +} from '@atproto/api' + +export type Embed = + | { + type: 'post' + view: AppBskyEmbedRecord.ViewRecord + } + | { + type: 'post_not_found' + view: AppBskyEmbedRecord.ViewNotFound + } + | { + type: 'post_blocked' + view: AppBskyEmbedRecord.ViewBlocked + } + | { + type: 'post_detached' + view: AppBskyEmbedRecord.ViewDetached + } + | { + type: 'feed' + view: AppBskyFeedDefs.GeneratorView + } + | { + type: 'list' + view: AppBskyGraphDefs.ListView + } + | { + type: 'labeler' + view: AppBskyLabelerDefs.LabelerView + } + | { + type: 'starter_pack' + view: AppBskyGraphDefs.StarterPackViewBasic + } + | { + type: 'images' + view: AppBskyEmbedImages.View + } + | { + type: 'link' + view: AppBskyEmbedExternal.View + } + | { + type: 'video' + view: AppBskyEmbedVideo.View + } + | { + type: 'post_with_media' + view: Embed + media: Embed + } + | { + type: 'unknown' + view: null + } + +export type EmbedType<T extends Embed['type']> = Extract<Embed, {type: T}> + +export function parseEmbedRecordView({record}: AppBskyEmbedRecord.View): Embed { + if (AppBskyEmbedRecord.isViewRecord(record)) { + return { + type: 'post', + view: record, + } + } else if (AppBskyEmbedRecord.isViewNotFound(record)) { + return { + type: 'post_not_found', + view: record, + } + } else if (AppBskyEmbedRecord.isViewBlocked(record)) { + return { + type: 'post_blocked', + view: record, + } + } else if (AppBskyEmbedRecord.isViewDetached(record)) { + return { + type: 'post_detached', + view: record, + } + } else if (AppBskyFeedDefs.isGeneratorView(record)) { + return { + type: 'feed', + view: record, + } + } else if (AppBskyGraphDefs.isListView(record)) { + return { + type: 'list', + view: record, + } + } else if (AppBskyLabelerDefs.isLabelerView(record)) { + return { + type: 'labeler', + view: record, + } + } else if (AppBskyGraphDefs.isStarterPackViewBasic(record)) { + return { + type: 'starter_pack', + view: record, + } + } else { + return { + type: 'unknown', + view: null, + } + } +} + +export function parseEmbed(embed: AppBskyFeedDefs.PostView['embed']): Embed { + if (AppBskyEmbedImages.isView(embed)) { + return { + type: 'images', + view: embed, + } + } else if (AppBskyEmbedExternal.isView(embed)) { + return { + type: 'link', + view: embed, + } + } else if (AppBskyEmbedVideo.isView(embed)) { + return { + type: 'video', + view: embed, + } + } else if (AppBskyEmbedRecord.isView(embed)) { + return parseEmbedRecordView(embed) + } else if (AppBskyEmbedRecordWithMedia.isView(embed)) { + return { + type: 'post_with_media', + view: parseEmbedRecordView(embed.record), + media: parseEmbed(embed.media), + } + } else { + return { + type: 'unknown', + view: null, + } + } +} diff --git a/bskyembed/src/util/pretty-number.ts b/bskyembed/src/util/pretty-number.ts new file mode 100644 index 000000000..07f7e9577 --- /dev/null +++ b/bskyembed/src/util/pretty-number.ts @@ -0,0 +1,9 @@ +const formatter = new Intl.NumberFormat('en-US', { + notation: 'compact', + maximumFractionDigits: 1, + roundingMode: 'trunc', +}) + +export function prettyNumber(number: number) { + return formatter.format(number) +} diff --git a/bskyembed/src/util/rkey.ts b/bskyembed/src/util/rkey.ts new file mode 100644 index 000000000..71d57f8c6 --- /dev/null +++ b/bskyembed/src/util/rkey.ts @@ -0,0 +1,6 @@ +import {AtUri} from '@atproto/api' + +export function getRkey({uri}: {uri: string}): string { + const at = new AtUri(uri) + return at.rkey +} diff --git a/bskyembed/src/util/verification-state.ts b/bskyembed/src/util/verification-state.ts new file mode 100644 index 000000000..29355e511 --- /dev/null +++ b/bskyembed/src/util/verification-state.ts @@ -0,0 +1,31 @@ +import * as bsky from '../types/bsky' + +export type VerificationState = { + role: 'default' | 'verifier' + isVerified: boolean +} + +export function getVerificationState({ + profile, +}: { + profile?: bsky.profile.AnyProfileView +}): VerificationState { + if (!profile || !profile.verification) { + return { + role: 'default', + isVerified: false, + } + } + + const {verifiedStatus, trustedVerifierStatus} = profile.verification + const isVerifiedUser = ['valid', 'invalid'].includes(verifiedStatus) + const isVerifierUser = ['valid', 'invalid'].includes(trustedVerifierStatus) + const isVerified = + (isVerifiedUser && verifiedStatus === 'valid') || + (isVerifierUser && trustedVerifierStatus === 'valid') + + return { + role: isVerifierUser ? 'verifier' : 'default', + isVerified, + } +} |