[SDK] Adopt @bsky.app/sdk and migrate RichText (#11369)

This commit is contained in:
Samuel Newman
2026-08-13 22:26:18 +03:00
committed by GitHub
parent d991118215
commit f6a22c4f05
47 changed files with 231 additions and 124 deletions
+11 -4
View File
@@ -13,9 +13,10 @@ import {
type ComAtprotoLabelDefs,
type ComAtprotoRepoApplyWrites,
type ComAtprotoRepoStrongRef,
RichText,
} from '@atproto/api'
import {TID} from '@atproto/common-web'
import {type Client} from '@atproto/lex'
import {RichText} from '@bsky.app/sdk/richtext'
import {t} from '@lingui/core/macro'
import {type QueryClient} from '@tanstack/react-query'
import {sha256} from 'js-sha256'
@@ -51,6 +52,12 @@ interface PostOpts {
replyTo?: string
onStateChange?: (state: string) => void
langs?: string[]
/*
* Facet/mention resolution is an appview job - it resolves handles through
* the appview, and the public fallback keeps it working when logged out.
* The rest of this pipeline still writes through the agent.
*/
appviewClient: Client
}
export async function post(
@@ -87,7 +94,7 @@ export async function post(
const draft = thread.posts[i]
// Not awaited to avoid waterfalls.
const rtPromise = resolveRT(agent, draft.richtext)
const rtPromise = resolveRT(opts.appviewClient, draft.richtext)
const embedPromise = resolveEmbed(
agent,
queryClient,
@@ -196,14 +203,14 @@ export async function post(
return {uris}
}
async function resolveRT(agent: AtpAgent, richtext: RichText) {
async function resolveRT(appviewClient: Client, richtext: RichText) {
const trimmedText = richtext.text
// Trim leading whitespace-only lines (but don't break ASCII art).
.replace(/^(\s*\n)+/, '')
// Trim any trailing whitespace.
.trimEnd()
let rt = new RichText({text: trimmedText}, {cleanNewlines: true})
await rt.detectFacets(agent)
await rt.detectFacets(appviewClient)
rt = shortenLinks(rt)
rt = stripInvalidMentions(rt)
+1 -1
View File
@@ -1,4 +1,4 @@
import {type RichText} from '@atproto/api'
import {type RichText} from '@bsky.app/sdk/richtext'
import {countGraphemes} from 'unicode-segmenter/grapheme'
import {shortenLinks} from './rich-text-manip'
+27 -2
View File
@@ -1,5 +1,7 @@
import {AppBskyRichtextFacet, type RichText} from '@atproto/api'
import {type RichText} from '@bsky.app/sdk/richtext'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {linkRequiresWarning} from './url-helpers'
export function richTextToString(rt: RichText, loose: boolean): string {
@@ -14,7 +16,7 @@ export function richTextToString(rt: RichText, loose: boolean): string {
for (const segment of rt.segments()) {
const link = segment.link
if (link && AppBskyRichtextFacet.validateLink(link).success) {
if (link && bsky.matches(app.bsky.richtext.facet.link, link)) {
const href = link.uri
const text = segment.text
@@ -28,3 +30,26 @@ export function richTextToString(rt: RichText, loose: boolean): string {
return result
}
/**
* Widens facets typed by the legacy `@atproto/api` codegen into the shape the
* SDK's `RichText` accepts.
*
* The two are the same lexicon and identical at runtime; they differ only in
* that the SDK brands `did`/`uri` as template literal types, which makes the
* legacy `string` versions unassignable. Call this where facets read off an
* `@atproto/api` view type are handed to `new RichText(...)`.
*
* Transitional: it goes away once the view types come from the SDK too.
*/
export function asSdkFacets(
facets: {index: {byteStart: number; byteEnd: number}; features: unknown[]}[],
): app.bsky.richtext.facet.Main[]
export function asSdkFacets(
facets:
| {index: {byteStart: number; byteEnd: number}; features: unknown[]}[]
| undefined,
): app.bsky.richtext.facet.Main[] | undefined
export function asSdkFacets(facets: unknown) {
return facets as app.bsky.richtext.facet.Main[] | undefined
}
+9 -3
View File
@@ -1,5 +1,7 @@
import {AppBskyRichtextFacet, type RichText, UnicodeString} from '@atproto/api'
import {type RichText, UnicodeString} from '@bsky.app/sdk/richtext'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {toShortUrl} from './url-helpers'
export function shortenLinks(rt: RichText): RichText {
@@ -10,7 +12,9 @@ export function shortenLinks(rt: RichText): RichText {
// enumerate the link facets
if (rt.facets) {
for (const facet of rt.facets) {
const isLink = !!facet.features.find(AppBskyRichtextFacet.isLink)
const isLink = !!facet.features.find(f =>
bsky.isType(app.bsky.richtext.facet.link, f),
)
if (!isLink) {
continue
}
@@ -40,7 +44,9 @@ export function stripInvalidMentions(rt: RichText): RichText {
rt = rt.clone()
if (rt.facets) {
rt.facets = rt.facets?.filter(facet => {
const mention = facet.features.find(AppBskyRichtextFacet.isMention)
const mention = facet.features.find(f =>
bsky.isType(app.bsky.richtext.facet.mention, f),
)
if (mention && !mention.did) {
return false
}