use bsky.app/sdk

This commit is contained in:
Samuel Newman
2026-07-17 11:19:59 +03:00
parent 6e17bc4ec7
commit 59811328c4
15 changed files with 429 additions and 544 deletions
+12 -11
View File
@@ -1,16 +1,17 @@
import {app} from '@bsky.app/sdk/lexicons'
import {ComponentChildren, h} from 'preact'
import {useMemo} from 'preact/hooks'
import infoIcon from '../../assets/circleInfo_stroke2_corner0_rounded.svg'
import {Link} from '#/components/link'
import {VerificationCheck} from '#/components/verification-check'
import {Globe} from '#/icons/Globe'
import {CONTENT_LABELS, labelsToInfo} from '#/labels'
import {getRkey} from '#/util/rkey'
import {getVerificationState} from '#/util/verification-state'
import infoIcon from '../../assets/arrowBottom_stroke2_corner0_rounded.svg'
import playIcon from '../../assets/play_filled_corner0_rounded.svg'
import starterPackIcon from '../../assets/starterPack.svg'
import {Globe} from '../icons/Globe'
import {CONTENT_LABELS, labelsToInfo} from '../labels'
import * as app from '../lexicons/app'
import {getRkey} from '../util/rkey'
import {getVerificationState} from '../util/verification-state'
import {Link} from './link'
import {VerificationCheck} from './verification-check'
export function Embed({
content,
@@ -32,7 +33,7 @@ export function Embed({
}
// Case 1b: Gallery (Photos v2)
if (AppBskyEmbedGallery.isView(content)) {
if (app.bsky.embed.gallery.view.isTypeOf(content)) {
return <GalleryEmbed content={content} labelInfo={labelInfo} />
}
@@ -253,14 +254,14 @@ function GalleryEmbed({
content,
labelInfo,
}: {
content: AppBskyEmbedGallery.View
content: app.bsky.embed.gallery.View
labelInfo?: string
}) {
if (labelInfo) {
return <Info>{labelInfo}</Info>
}
const images = content.items
.filter(AppBskyEmbedGallery.isViewImage)
.filter(i => app.bsky.embed.gallery.viewImage.isTypeOf(i))
.map(i => ({thumb: i.thumbnail, alt: i.alt}))
return <ImageGrid images={images} />
}
+17 -16
View File
@@ -1,21 +1,22 @@
import {app} from '@bsky.app/sdk/lexicons'
import {RichText} from '@bsky.app/sdk/richtext'
import {ComponentChild, h} from 'preact'
import {Container} from '#/components/container'
import {Embed} from '#/components/embed'
import {Link} from '#/components/link'
import {VerificationCheck} from '#/components/verification-check'
import {Like as LikeIcon} from '#/icons/Like'
import {Reply as ReplyIcon} from '#/icons/Reply'
import {Repost as RepostIcon} from '#/icons/Repost'
import {Robot as RobotIcon} from '#/icons/Robot'
import {CONTENT_LABELS} from '#/labels'
import {niceDate} from '#/util/nice-date'
import {prettyNumber} from '#/util/pretty-number'
import {getRkey} from '#/util/rkey'
import {getVerificationState} from '#/util/verification-state'
import logo from '../../assets/logo_full_name.svg'
import {Like as LikeIcon} from '../icons/Like'
import {Reply as ReplyIcon} from '../icons/Reply'
import {Repost as RepostIcon} from '../icons/Repost'
import {Robot as RobotIcon} from '../icons/Robot'
import {CONTENT_LABELS} from '../labels'
import * as app from '../lexicons/app'
import {FacetRenderer} from '../util/facet-renderer'
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 {
post: app.bsky.feed.defs.PostView
@@ -147,7 +148,7 @@ function PostContent({record}: {record: app.bsky.feed.post.Main | null}) {
// render an empty <p> that adds an extra flex gap above the embed
if (!record?.text) return null
const rt = new FacetRenderer({text: record.text, facets: record.facets})
const rt = new RichText({text: record.text, facets: record.facets})
const richText: ComponentChild[] = []
let counter = 0
+1 -1
View File
@@ -1,4 +1,4 @@
import * as app from './lexicons/app'
import {app} from '@bsky.app/sdk/lexicons'
export const CONTENT_LABELS = ['porn', 'sexual', 'nudity', 'graphic-media']
+10 -10
View File
@@ -2,22 +2,22 @@ import '../index.css'
import {AtUriString, Client, type HandleString} from '@atproto/lex'
import {AtUri} from '@atproto/syntax'
import {app, com} from '@bsky.app/sdk/lexicons'
import {h, render} from 'preact'
import {useEffect, useMemo, useRef, useState} from 'preact/hooks'
import arrowBottom from '../../assets/arrowBottom_stroke2_corner0_rounded.svg'
import logo from '../../assets/logo.svg'
import {
assertColorModeValues,
ColorModeValues,
initSystemColorMode,
} from '../color-mode'
import {Container} from '../components/container'
import {Link} from '../components/link'
import {Post} from '../components/post'
import * as app from '../lexicons/app'
import * as com from '../lexicons/com'
import {niceDate} from '../util/nice-date'
} from '#/color-mode'
import {Container} from '#/components/container'
import {Link} from '#/components/link'
import {Post} from '#/components/post'
import {niceDate} from '#/util/nice-date'
import arrowBottom from '../../assets/arrowBottom_stroke2_corner0_rounded.svg'
import logo from '../../assets/logo.svg'
const DEFAULT_POST =
'https://bsky.app/profile/did:plc:vjug55kidv6sye7ykr5faxxn/post/3jzn6g7ixgq2y'
@@ -85,7 +85,7 @@ function LandingPage() {
atUri = `at://${did}/app.bsky.feed.post/${rkey}`
} catch (err) {
console.log(err)
throw new Error('Invalid Bluesky URL')
throw new Error('Invalid Bluesky URL', {cause: err})
}
}
}
+7 -6
View File
@@ -1,15 +1,16 @@
import '../index.css'
import {Client, isAtUriString} from '@atproto/lex'
import {app} from '@bsky.app/sdk/lexicons'
import {h, render} from 'preact'
import {applyTheme, initSystemColorMode} from '#/color-mode'
import {Container} from '#/components/container'
import {Link} from '#/components/link'
import {Post} from '#/components/post'
import {getRkey} from '#/util/rkey'
import logo from '../../assets/logo.svg'
import {applyTheme, initSystemColorMode} from '../color-mode'
import {Container} from '../components/container'
import {Link} from '../components/link'
import {Post} from '../components/post'
import * as app from '../lexicons/app'
import {getRkey} from '../util/rkey'
const root = document.getElementById('app')
if (!root) throw new Error('No root element')
-78
View File
@@ -1,78 +0,0 @@
import * as app from '../lexicons/app'
type Facet = app.bsky.richtext.facet.Main
const encoder = new TextEncoder()
const decoder = new TextDecoder()
export class FacetSegment {
constructor(
public text: string,
public facet?: Facet,
) {}
get link() {
return this.facet?.features.find(app.bsky.richtext.facet.link.$isTypeOf)
}
get mention() {
return this.facet?.features.find(app.bsky.richtext.facet.mention.$isTypeOf)
}
get tag() {
return this.facet?.features.find(app.bsky.richtext.facet.tag.$isTypeOf)
}
}
export class FacetRenderer {
private utf8: Uint8Array
private facets: Facet[]
constructor({text, facets}: {text: string; facets?: Facet[]}) {
this.utf8 = encoder.encode(text)
this.facets = (facets ?? [])
.filter(f => f.index.byteStart <= f.index.byteEnd)
.sort((a, b) => a.index.byteStart - b.index.byteStart)
}
private slice(start: number, end: number): string {
return decoder.decode(this.utf8.slice(start, end))
}
*segments(): Generator<FacetSegment, void, void> {
if (!this.facets.length) {
yield new FacetSegment(decoder.decode(this.utf8))
return
}
let textCursor = 0
let facetCursor = 0
do {
const currFacet = this.facets[facetCursor]
if (textCursor < currFacet.index.byteStart) {
yield new FacetSegment(
this.slice(textCursor, currFacet.index.byteStart),
)
} else if (textCursor > currFacet.index.byteStart) {
facetCursor++
continue
}
if (currFacet.index.byteStart < currFacet.index.byteEnd) {
const subtext = this.slice(
currFacet.index.byteStart,
currFacet.index.byteEnd,
)
if (!subtext.trim()) {
yield new FacetSegment(subtext)
} else {
yield new FacetSegment(subtext, currFacet)
}
}
textCursor = currFacet.index.byteEnd
facetCursor++
} while (facetCursor < this.facets.length)
if (textCursor < this.utf8.byteLength) {
yield new FacetSegment(this.slice(textCursor, this.utf8.byteLength))
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import * as app from '../lexicons/app'
import {app} from '@bsky.app/sdk/lexicons'
export type VerificationState = {
role: 'default' | 'verifier'