[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
+2 -1
View File
@@ -49,10 +49,10 @@ import {
AppBskyUnspeccedDefs,
AtUri,
ChatBskyGroupDefs,
type RichText,
} from '@atproto/api'
import {type Client} from '@atproto/lex'
import {type AtUriString} from '@atproto/syntax'
import {type RichText} from '@bsky.app/sdk/richtext'
import {plural} from '@lingui/core/macro'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
@@ -1083,6 +1083,7 @@ export const ComposePost = ({
replyTo: replyTo?.uri,
onStateChange: setPublishingStage,
langs: currentLanguages,
appviewClient: client,
})
).uris[0]
+2 -1
View File
@@ -1,7 +1,8 @@
/**
* Type converters for Draft API - convert between ComposerState and server Draft types.
*/
import {AppBskyDraftDefs, AtUri, RichText} from '@atproto/api'
import {AppBskyDraftDefs, AtUri} from '@atproto/api'
import {RichText} from '@bsky.app/sdk/richtext'
import {nanoid} from 'nanoid/non-secure'
import {resolveLink} from '#/lib/api/resolve'
@@ -1,11 +1,11 @@
import {useEffect, useMemo, useRef, useState} from 'react'
import {Platform, Text as RNText, View} from 'react-native'
import {RichText} from '@atproto/api'
import {parseLanguageString} from '@atproto/syntax'
import {
guessLanguageAsync,
type LanguageResult,
} from '@bsky.app/expo-guess-language'
import {RichText} from '@bsky.app/sdk/richtext'
import {Trans, useLingui} from '@lingui/react/macro'
import debounce from 'lodash.debounce'
+4 -3
View File
@@ -3,9 +3,8 @@ import {
type AppBskyActorDefs,
type AppBskyDraftDefs,
type AppBskyFeedPostgate,
AppBskyRichtextFacet,
RichText,
} from '@atproto/api'
import {RichText} from '@bsky.app/sdk/richtext'
import {nanoid} from 'nanoid/non-secure'
import {type VideoTelemetry} from '#/lib/media/video/telemetry'
@@ -28,6 +27,8 @@ import {
suggestLinkCardUri,
} from '#/view/com/composer/text-input/text-input-util'
import {type Gif} from '#/features/gifPicker/types'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {
createVideoState,
type VideoAction,
@@ -677,7 +678,7 @@ export function createComposerState({
if (initRichText.facets) {
for (const facet of initRichText.facets) {
for (const feature of facet.features) {
if (AppBskyRichtextFacet.isLink(feature)) {
if (bsky.isType(app.bsky.richtext.facet.link, feature)) {
if (isBskyPostUrl(feature.uri)) {
detectedPostUris.set(feature.uri, {facet, rt: initRichText})
} else {
@@ -12,7 +12,7 @@ import {
View,
} from 'react-native'
import {type PasteEventPayload, TextInputWrapper} from 'expo-paste-input'
import {AppBskyRichtextFacet, RichText} from '@atproto/api'
import {RichText} from '@bsky.app/sdk/richtext'
import {useLingui} from '@lingui/react/macro'
import {IMAGE_SIZE_CONFIG_POSTS} from '#/lib/constants'
@@ -27,6 +27,8 @@ import {
import {atoms as a, useAlf} from '#/alf'
import {normalizeTextStyles} from '#/alf/typography'
import {IS_ANDROID} from '#/env'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {Autocomplete} from './mobile/Autocomplete'
import {type TextInputProps} from './TextInput.types'
@@ -88,7 +90,7 @@ export function TextInput({
if (newRt.facets) {
for (const facet of newRt.facets) {
for (const feature of facet.features) {
if (AppBskyRichtextFacet.isLink(feature)) {
if (bsky.isType(app.bsky.richtext.facet.link, feature)) {
if (isUriImage(feature.uri)) {
const res = await downloadAndResize({
uri: feature.uri,
@@ -1,5 +1,5 @@
import {type TextInput} from 'react-native'
import {type RichText} from '@atproto/api'
import {type RichText} from '@bsky.app/sdk/richtext'
export type TextInputRef = {
focus: () => void
@@ -8,7 +8,7 @@ import {
} from 'react'
import {StyleSheet, View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {AppBskyRichtextFacet, RichText} from '@atproto/api'
import {RichText} from '@bsky.app/sdk/richtext'
import {Trans} from '@lingui/react/macro'
import {getSchema} from '@tiptap/core'
import {Document} from '@tiptap/extension-document'
@@ -40,6 +40,8 @@ import {normalizeTextStyles} from '#/alf/typography'
import {type Emoji} from '#/components/EmojiPicker'
import {Portal} from '#/components/Portal'
import {Text} from '#/components/Typography'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {type TextInputProps} from './TextInput.types'
import {type AutocompleteRef, createSuggestion} from './web/Autocomplete'
import {LinkDecorator} from './web/LinkDecorator'
@@ -265,7 +267,7 @@ export function TextInput({
if (newRt.facets) {
for (const facet of newRt.facets) {
for (const feature of facet.features) {
if (AppBskyRichtextFacet.isLink(feature)) {
if (bsky.isType(app.bsky.richtext.facet.link, feature)) {
nextDetectedUris.set(feature.uri, {facet, rt: newRt})
}
}
@@ -1,8 +1,10 @@
import {type AppBskyRichtextFacet, type RichText} from '@atproto/api'
import {type RichText} from '@bsky.app/sdk/richtext'
import {type app} from '#/lexicons'
export type LinkFacetMatch = {
rt: RichText
facet: AppBskyRichtextFacet.Main
facet: app.bsky.richtext.facet.Main
}
export function suggestLinkCardUri(
+3 -2
View File
@@ -6,14 +6,15 @@ import {
AtUri,
moderatePost,
type ModerationDecision,
RichText as RichTextAPI,
} from '@atproto/api'
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {useQueryClient} from '@tanstack/react-query'
import {MAX_POST_LINES} from '#/lib/constants'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {makeProfileLink} from '#/lib/routes/links'
import {countLines} from '#/lib/strings/helpers'
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
import {
POST_TOMBSTONE,
type Shadow,
@@ -68,7 +69,7 @@ export function Post({
record
? new RichTextAPI({
text: record.text,
facets: record.facets,
facets: asSdkFacets(record.facets),
})
: undefined,
[record],
+1 -1
View File
@@ -25,8 +25,8 @@ import {
AppBskyEmbedImages,
AppBskyEmbedVideo,
type AppBskyFeedDefs,
type RichText as RichTextType,
} from '@atproto/api'
import {type RichText as RichTextType} from '@bsky.app/sdk/richtext'
import {useLingui} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query'
+3 -2
View File
@@ -7,8 +7,8 @@ import {
AppBskyFeedThreadgate,
AtUri,
type ModerationDecision,
RichText as RichTextAPI,
} from '@atproto/api'
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {useQueryClient} from '@tanstack/react-query'
import {type ReasonFeedSource} from '#/lib/api/feed/types'
@@ -17,6 +17,7 @@ import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {usePalette} from '#/lib/hooks/usePalette'
import {makeProfileLink} from '#/lib/routes/links'
import {countLines} from '#/lib/strings/helpers'
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
import {
POST_TOMBSTONE,
type Shadow,
@@ -104,7 +105,7 @@ export function PostFeedItem({
() =>
new RichTextAPI({
text: record.text,
facets: record.facets,
facets: asSdkFacets(record.facets),
}),
[record],
)
+1 -1
View File
@@ -15,8 +15,8 @@ import {
type ModerationBehavior,
type ModerationDecision,
type ModerationOpts,
RichText,
} from '@atproto/api'
import {RichText} from '@bsky.app/sdk/richtext'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
+9 -5
View File
@@ -6,8 +6,8 @@ import {
type AppBskyActorDefs,
moderateProfile,
type ModerationOpts,
RichText as RichTextAPI,
} from '@atproto/api'
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -33,7 +33,7 @@ import {useLabelerInfoQuery} from '#/state/queries/labeler'
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
import {useProfileQuery} from '#/state/queries/profile'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useAgent, useSession} from '#/state/session'
import {useAppviewClient, useSession} from '#/state/session'
import {ProfileFeedgens} from '#/view/com/feeds/ProfileFeedgens'
import {ProfileLists} from '#/view/com/lists/ProfileLists'
import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
@@ -613,7 +613,11 @@ function ProfileScreenLoaded({
}
function useRichText(text: string): [RichTextAPI, boolean] {
const agent = useAgent()
/*
* Facet/mention resolution is an appview job - it resolves handles through
* the appview, and the public fallback keeps it working when logged out.
*/
const client = useAppviewClient()
const [prevText, setPrevText] = useState(text)
const [rawRT, setRawRT] = useState(() => new RichTextAPI({text}))
const [resolvedRT, setResolvedRT] = useState<RichTextAPI | null>(null)
@@ -628,7 +632,7 @@ function useRichText(text: string): [RichTextAPI, boolean] {
async function resolveRTFacets() {
// new each time
const resolvedRT = new RichTextAPI({text})
await resolvedRT.detectFacets(agent)
await resolvedRT.detectFacets(client)
if (!ignore) {
setResolvedRT(resolvedRT)
}
@@ -637,7 +641,7 @@ function useRichText(text: string): [RichTextAPI, boolean] {
return () => {
ignore = true
}
}, [text, agent])
}, [text, client])
const isResolving = resolvedRT === null
return [resolvedRT ?? rawRT, isResolving]
}