From 2beb63fb2dec5a5a0beac3b98830dad5226e2dd2 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 3 Aug 2026 22:33:04 +0300 Subject: [PATCH] migrate RichText to the SDK and resolve facets via the appview The two RichText classes are not mutually assignable - `UnicodeString` has a private field and the SDK brands `did`/`uri` as template literal types - so every producer and consumer of a RichText instance has to move in one step. `detectFacets` now takes a lex client instead of the legacy agent, which is what removes the last hard agent dependency from these files. Handle resolution is an appview job, so the appview client is threaded in: through `useAppviewClient` in the hooks and dialogs, and through a new `appviewClient` option on `apilib.post` (Composer already had the client to hand). The rest of the post pipeline still writes through the agent. Facet feature checks move from the `AppBskyRichtextFacet` validators to the generated `#/lexicons` schemas, matching how the rest of the app narrows lexicon types. Display sinks still read facets off `@atproto/api` view types, which are the same lexicon but typed with plain strings. `asSdkFacets` widens them at those call sites and goes away once the view types come from the SDK too. --- src/components/FeedCard.tsx | 8 ++--- src/components/Post/Embed/index.tsx | 5 ++-- .../PostControls/PostMenu/PostMenuItems.tsx | 2 +- .../PostControls/PostMenu/index.tsx | 2 +- .../ShareMenu/ShareMenuItems.types.tsx | 2 +- .../PostControls/ShareMenu/index.tsx | 2 +- src/components/PostControls/index.tsx | 2 +- src/components/ProfileCard.tsx | 7 ++--- src/components/RichText.tsx | 10 ++++--- .../dialogs/lists/CreateOrEditListDialog.tsx | 22 +++++++++----- src/components/dms/MessageContextMenu.tsx | 11 +++---- src/components/dms/MessageItem.tsx | 8 +++-- src/components/hooks/useRichText.ts | 15 ++++++---- src/lib/api/index.ts | 15 +++++++--- src/lib/strings/helpers.ts | 2 +- src/lib/strings/rich-text-helpers.ts | 29 +++++++++++++++++-- src/lib/strings/rich-text-manip.ts | 12 ++++++-- .../Messages/components/MessageInputEmbed.tsx | 15 ++++------ .../Messages/components/MessagesList.tsx | 20 ++++++++----- .../components/ThreadItemAnchor.tsx | 5 ++-- .../PostThread/components/ThreadItemPost.tsx | 5 ++-- .../components/ThreadItemTreePost.tsx | 5 ++-- .../Profile/Header/ProfileHeaderLabeler.tsx | 2 +- .../Profile/Header/ProfileHeaderStandard.tsx | 2 +- src/screens/Profile/Header/index.tsx | 2 +- src/screens/ProfileList/components/Header.tsx | 6 ++-- .../Search/modules/ExploreTrendingTopics.tsx | 7 ++--- src/screens/StarterPack/StarterPackScreen.tsx | 5 ++-- src/screens/VideoFeed/index.tsx | 5 ++-- src/state/queries/feed.ts | 7 +++-- src/state/queries/starter-packs.ts | 20 ++++++++----- src/view/com/composer/Composer.tsx | 3 +- src/view/com/composer/drafts/state/api.ts | 3 +- .../select-language/SuggestedLanguage.tsx | 2 +- src/view/com/composer/state/composer.ts | 7 +++-- .../com/composer/text-input/TextInput.tsx | 6 ++-- .../composer/text-input/TextInput.types.ts | 2 +- .../com/composer/text-input/TextInput.web.tsx | 6 ++-- .../composer/text-input/text-input-util.ts | 6 ++-- src/view/com/post/Post.tsx | 5 ++-- src/view/com/posts/PostFeed.tsx | 2 +- src/view/com/posts/PostFeedItem.tsx | 5 ++-- src/view/screens/DebugMod.tsx | 2 +- src/view/screens/Profile.tsx | 14 +++++---- 44 files changed, 200 insertions(+), 123 deletions(-) diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx index efd14be4ba..2e7b070fb1 100644 --- a/src/components/FeedCard.tsx +++ b/src/components/FeedCard.tsx @@ -1,11 +1,7 @@ import {useCallback, useEffect, useMemo} from 'react' import {type GestureResponderEvent, View} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyGraphDefs, - AtUri, - RichText as RichTextApi, -} from '@atproto/api' +import {type AppBskyFeedDefs, type AppBskyGraphDefs, AtUri} from '@atproto/api' +import {RichText as RichTextApi} from '@bsky.app/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' diff --git a/src/components/Post/Embed/index.tsx b/src/components/Post/Embed/index.tsx index da930dc6fb..0b143a0df3 100644 --- a/src/components/Post/Embed/index.tsx +++ b/src/components/Post/Embed/index.tsx @@ -6,12 +6,13 @@ import { AppBskyFeedPost, AtUri, moderatePost, - RichText as RichTextAPI, } from '@atproto/api' +import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' import {makeProfileLink} from '#/lib/routes/links' +import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {getChatInviteCodeFromUrl} from '#/lib/strings/url-helpers' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {unstableCacheProfileView} from '#/state/queries/profile' @@ -295,7 +296,7 @@ export function QuoteEmbed({ return undefined const {text, facets} = quote.record return text.trim() - ? new RichTextAPI({text: text, facets: facets}) + ? new RichTextAPI({text: text, facets: asSdkFacets(facets)}) : undefined }, [quote.record]) diff --git a/src/components/PostControls/PostMenu/PostMenuItems.tsx b/src/components/PostControls/PostMenu/PostMenuItems.tsx index b51f6cb7cb..43fdaaf1cd 100644 --- a/src/components/PostControls/PostMenu/PostMenuItems.tsx +++ b/src/components/PostControls/PostMenu/PostMenuItems.tsx @@ -11,8 +11,8 @@ import { type AppBskyFeedPost, type AppBskyFeedThreadgate, AtUri, - type RichText as RichTextAPI, } from '@atproto/api' +import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' diff --git a/src/components/PostControls/PostMenu/index.tsx b/src/components/PostControls/PostMenu/index.tsx index a744efbaa8..c0826683b6 100644 --- a/src/components/PostControls/PostMenu/index.tsx +++ b/src/components/PostControls/PostMenu/index.tsx @@ -4,8 +4,8 @@ import { type AppBskyFeedDefs, type AppBskyFeedPost, type AppBskyFeedThreadgate, - type RichText as RichTextAPI, } from '@atproto/api' +import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {useLingui} from '@lingui/react/macro' import {type Shadow} from '#/state/cache/post-shadow' diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx index 5bc2a8fb6e..ef93865513 100644 --- a/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx +++ b/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx @@ -3,8 +3,8 @@ import { type AppBskyFeedDefs, type AppBskyFeedPost, type AppBskyFeedThreadgate, - type RichText as RichTextAPI, } from '@atproto/api' +import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {type Shadow} from '#/state/cache/post-shadow' diff --git a/src/components/PostControls/ShareMenu/index.tsx b/src/components/PostControls/ShareMenu/index.tsx index efac6a3f01..121cd2a779 100644 --- a/src/components/PostControls/ShareMenu/index.tsx +++ b/src/components/PostControls/ShareMenu/index.tsx @@ -5,8 +5,8 @@ import { type AppBskyFeedPost, type AppBskyFeedThreadgate, AtUri, - type RichText as RichTextAPI, } from '@atproto/api' +import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' diff --git a/src/components/PostControls/index.tsx b/src/components/PostControls/index.tsx index 1f6be5ab6c..88f30b0561 100644 --- a/src/components/PostControls/index.tsx +++ b/src/components/PostControls/index.tsx @@ -4,8 +4,8 @@ import { type AppBskyFeedDefs, type AppBskyFeedPost, type AppBskyFeedThreadgate, - type RichText as RichTextAPI, } from '@atproto/api' +import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index c37f99c075..8508fecc71 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -6,11 +6,8 @@ import { View, type ViewStyle, } from 'react-native' -import { - moderateProfile, - type ModerationOpts, - RichText as RichTextApi, -} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {RichText as RichTextApi} from '@bsky.app/sdk/richtext' import {useLingui} from '@lingui/react/macro' import {getModerationCauseKey} from '#/lib/moderation' diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index 5501193faf..ce73990570 100644 --- a/src/components/RichText.tsx +++ b/src/components/RichText.tsx @@ -1,6 +1,6 @@ import {useMemo} from 'react' import {type StyleProp, type TextStyle} from 'react-native' -import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api' +import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {toShortUrl} from '#/lib/strings/url-helpers' import {android, atoms as a, flatten, type TextStyleProp} from '#/alf' @@ -9,6 +9,8 @@ import {InlineLinkText, type LinkProps} from '#/components/Link' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {RichTextTag} from '#/components/RichTextTag' import {Text, type TextProps} from '#/components/Typography' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' const WORD_WRAP = {wordWrap: 1} // lifted from facet detection in `RichText` impl, _without_ `gm` flags @@ -139,7 +141,7 @@ export function RichText({ if ( mention && (disableMentionFacetValidation || - AppBskyRichtextFacet.validateMention(mention).success) && + bsky.matches(app.bsky.richtext.facet.mention, mention)) && !disableLinks ) { els.push( @@ -156,7 +158,7 @@ export function RichText({ , ) - } else if (link && AppBskyRichtextFacet.validateLink(link).success) { + } else if (link && bsky.matches(app.bsky.richtext.facet.link, link)) { const isValidLink = URL_REGEX.test(link.uri) if (!isValidLink || disableLinks) { els.push(toShortUrl(segment.text)) @@ -181,7 +183,7 @@ export function RichText({ !disableLinks && enableTags && tag && - AppBskyRichtextFacet.validateTag(tag).success + bsky.matches(app.bsky.richtext.facet.tag, tag) ) { els.push( new RichTextAPI({text})) const [resolvedRT, setResolvedRT] = useState(null) - const agent = useAgent() + /* + * Facet/mention resolution is an appview job - it resolves handles via + * `com.atproto.identity.resolveHandle` through the appview. The public + * fallback keeps mentions working on logged-out surfaces. + */ + const client = useAppviewClient() if (text !== prevText) { setPrevText(text) setRawRT(new RichTextAPI({text})) @@ -19,7 +24,7 @@ export 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) } @@ -28,7 +33,7 @@ export function useRichText(text: string): [RichTextAPI, boolean] { return () => { ignore = true } - }, [text, agent]) + }, [text, client]) const isResolving = resolvedRT === null return [resolvedRT ?? rawRT, isResolving] } diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 69cc2bf03e..9f81abb77a 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -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) diff --git a/src/lib/strings/helpers.ts b/src/lib/strings/helpers.ts index 77ef18cb55..402807f50f 100644 --- a/src/lib/strings/helpers.ts +++ b/src/lib/strings/helpers.ts @@ -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' diff --git a/src/lib/strings/rich-text-helpers.ts b/src/lib/strings/rich-text-helpers.ts index c2b2ceac52..a6341025ff 100644 --- a/src/lib/strings/rich-text-helpers.ts +++ b/src/lib/strings/rich-text-helpers.ts @@ -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 +} diff --git a/src/lib/strings/rich-text-manip.ts b/src/lib/strings/rich-text-manip.ts index 099fbffb0a..fa7a9b4a82 100644 --- a/src/lib/strings/rich-text-manip.ts +++ b/src/lib/strings/rich-text-manip.ts @@ -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 } diff --git a/src/screens/Messages/components/MessageInputEmbed.tsx b/src/screens/Messages/components/MessageInputEmbed.tsx index 477939cd3e..9d1e4c32f6 100644 --- a/src/screens/Messages/components/MessageInputEmbed.tsx +++ b/src/screens/Messages/components/MessageInputEmbed.tsx @@ -1,12 +1,7 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {LayoutAnimation, View} from 'react-native' -import { - AppBskyFeedPost, - AppBskyRichtextFacet, - AtUri, - moderatePost, - RichText as RichTextAPI, -} from '@atproto/api' +import {AppBskyFeedPost, AtUri, moderatePost} from '@atproto/api' +import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans, useLingui} from '@lingui/react/macro' import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native' @@ -16,6 +11,7 @@ import { type CommonNavigatorParams, type NavigationProp, } from '#/lib/routes/types' +import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import { convertBskyAppUrlIfNeeded, getChatInviteCodeFromUrl, @@ -36,6 +32,7 @@ import {ContentHider} from '#/components/moderation/ContentHider' import {PostAlerts} from '#/components/moderation/PostAlerts' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' /** @@ -106,7 +103,7 @@ export function useExtractEmbedFromFacets( for (const facet of rt.facets ?? []) { for (const feature of facet.features) { if ( - AppBskyRichtextFacet.isLink(feature) && + bsky.isType(app.bsky.richtext.facet.link, feature) && (isBskyPostUrl(feature.uri) || isBskyChatInviteUrl(feature.uri)) ) { uriFromFacet = feature.uri @@ -176,7 +173,7 @@ function MessageInputPostEmbed({ return { rt: new RichTextAPI({ text: post.record.text, - facets: post.record.facets, + facets: asSdkFacets(post.record.facets), }), record: post.record, } diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx index 312a2277ad..e9d336a67a 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -25,13 +25,12 @@ import {scheduleOnRN} from 'react-native-worklets' import { type $Typed, type AppBskyEmbedRecord, - AppBskyRichtextFacet, ChatBskyConvoDefs, type ChatBskyEmbedJoinLink, ChatBskyGroupDefs, - RichText, } from '@atproto/api' import {useScrollEdgeEffectRef} from '@bsky.app/expo-scroll-edge-effect' +import {RichText} from '@bsky.app/sdk/richtext' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {mergeRefs} from '#/lib/merge-refs' @@ -52,7 +51,7 @@ import {type ConvoState, ConvoStatus} from '#/state/messages/convo/types' import {useGetJoinLinkPreview} from '#/state/queries/join-links' import {useGetPost} from '#/state/queries/post' import {createEmbedViewRecordFromPost} from '#/state/queries/postgate/util' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, useSession} from '#/state/session' import {List, type ListMethods} from '#/view/com/util/List' import {MessageComposer} from '#/screens/Messages/components/MessageComposer' import {MessageListError} from '#/screens/Messages/components/MessageListError' @@ -71,6 +70,8 @@ import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_ANDROID, IS_NATIVE, IS_WEB} from '#/env' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {ChatStatusInfo} from './ChatStatusInfo' import {groupSystemMessages, type RenderItem} from './groupSystemMessages' import {InviteLinkDialogProvider} from './InviteLinkDialogProvider' @@ -144,7 +145,11 @@ export function MessagesList({ }) { const ax = useAnalytics() const convoState = useConvoActive() - 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 appviewClient = useAppviewClient() const {hasSession, currentAccount} = useSession() const getPost = useGetPost() const getJoinLinkPreview = useGetJoinLinkPreview() @@ -550,7 +555,8 @@ export function MessagesList({ const linkFacet = rt.facets?.find(facet => facet.features.find( feature => - AppBskyRichtextFacet.isLink(feature) && predicate(feature.uri), + bsky.isType(app.bsky.richtext.facet.link, feature) && + predicate(feature.uri), ), ) if (linkFacet) { @@ -617,7 +623,7 @@ export function MessagesList({ replyTo = {messageId: reply.id} } - await rt.detectFacets(agent) + await rt.detectFacets(appviewClient) rt = shortenLinks(rt) rt = stripInvalidMentions(rt) @@ -667,7 +673,7 @@ export function MessagesList({ } }, [ - agent, + appviewClient, convoState, getPost, getJoinLinkPreview, diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx index c4d664238b..4a2f6513e5 100644 --- a/src/screens/PostThread/components/ThreadItemAnchor.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx @@ -5,8 +5,8 @@ import { AppBskyFeedPost, type AppBskyFeedThreadgate, AtUri, - RichText as RichTextAPI, } from '@atproto/api' +import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' @@ -14,6 +14,7 @@ import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' +import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {niceDate} from '#/lib/strings/time' import { POST_TOMBSTONE, @@ -199,7 +200,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ () => new RichTextAPI({ text: record.text, - facets: record.facets, + facets: asSdkFacets(record.facets), }), [record], ) diff --git a/src/screens/PostThread/components/ThreadItemPost.tsx b/src/screens/PostThread/components/ThreadItemPost.tsx index c7f5b3b5f7..bd256e43b0 100644 --- a/src/screens/PostThread/components/ThreadItemPost.tsx +++ b/src/screens/PostThread/components/ThreadItemPost.tsx @@ -4,14 +4,15 @@ import { type AppBskyFeedDefs, type AppBskyFeedThreadgate, AtUri, - RichText as RichTextAPI, } from '@atproto/api' +import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans} from '@lingui/react/macro' 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, @@ -211,7 +212,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({ () => new RichTextAPI({ text: record.text, - facets: record.facets, + facets: asSdkFacets(record.facets), }), [record], ) diff --git a/src/screens/PostThread/components/ThreadItemTreePost.tsx b/src/screens/PostThread/components/ThreadItemTreePost.tsx index 1560550329..3f4ae03741 100644 --- a/src/screens/PostThread/components/ThreadItemTreePost.tsx +++ b/src/screens/PostThread/components/ThreadItemTreePost.tsx @@ -4,14 +4,15 @@ import { type AppBskyFeedDefs, type AppBskyFeedThreadgate, AtUri, - RichText as RichTextAPI, } from '@atproto/api' +import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans} from '@lingui/react/macro' 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, @@ -271,7 +272,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({ () => new RichTextAPI({ text: record.text, - facets: record.facets, + facets: asSdkFacets(record.facets), }), [record], ) diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index 96e136da5c..812e65b6e2 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -5,8 +5,8 @@ import { type AppBskyLabelerDefs, moderateProfile, type ModerationOpts, - type RichText as RichTextAPI, } from '@atproto/api' +import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg, plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index da01ded772..3070997b98 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -5,8 +5,8 @@ import { moderateProfile, type ModerationDecision, type ModerationOpts, - type RichText as RichTextAPI, } from '@atproto/api' +import {type 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' diff --git a/src/screens/Profile/Header/index.tsx b/src/screens/Profile/Header/index.tsx index 0b7ab16ec4..dbd8bafcab 100644 --- a/src/screens/Profile/Header/index.tsx +++ b/src/screens/Profile/Header/index.tsx @@ -12,8 +12,8 @@ import { type AppBskyLabelerDefs, moderateProfile, type ModerationOpts, - type RichText as RichTextAPI, } from '@atproto/api' +import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {useIsFocused} from '@react-navigation/native' import {sanitizeHandle} from '#/lib/strings/handles' diff --git a/src/screens/ProfileList/components/Header.tsx b/src/screens/ProfileList/components/Header.tsx index 9d6c4f3ff7..55f572c554 100644 --- a/src/screens/ProfileList/components/Header.tsx +++ b/src/screens/ProfileList/components/Header.tsx @@ -1,12 +1,14 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api' +import {AppBskyGraphDefs} 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' import {useHaptics} from '#/lib/haptics' import {makeListLink} from '#/lib/routes/links' +import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {logger} from '#/logger' import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list' import { @@ -128,7 +130,7 @@ export function Header({ list.description ? new RichTextAPI({ text: list.description, - facets: list.descriptionFacets, + facets: asSdkFacets(list.descriptionFacets), }) : undefined, [list], diff --git a/src/screens/Search/modules/ExploreTrendingTopics.tsx b/src/screens/Search/modules/ExploreTrendingTopics.tsx index 38c8cac0a8..60c42323c3 100644 --- a/src/screens/Search/modules/ExploreTrendingTopics.tsx +++ b/src/screens/Search/modules/ExploreTrendingTopics.tsx @@ -1,11 +1,8 @@ import {useMemo} from 'react' import {Pressable, View} from 'react-native' import {Image} from 'expo-image' -import { - type AppBskyUnspeccedDefs, - moderateProfile, - RichText as RichTextApi, -} from '@atproto/api' +import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api' +import {RichText as RichTextApi} from '@bsky.app/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useModerationOpts} from '#/state/preferences/moderation-opts' diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx index ddbd5cd840..bb9c6506e9 100644 --- a/src/screens/StarterPack/StarterPackScreen.tsx +++ b/src/screens/StarterPack/StarterPackScreen.tsx @@ -6,8 +6,8 @@ import { AppBskyGraphStarterpack, AtUri, 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 {Plural, Trans} from '@lingui/react/macro' @@ -24,6 +24,7 @@ import { type NavigationProp, } from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' +import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {getStarterPackOgCard} from '#/lib/strings/starter-pack' import {logger} from '#/logger' import {updateProfileShadow} from '#/state/cache/profile-shadow' @@ -418,7 +419,7 @@ function Header({ const richText = record.description ? new RichTextAPI({ text: record.description, - facets: record.descriptionFacets, + facets: asSdkFacets(record.descriptionFacets), }) : undefined diff --git a/src/screens/VideoFeed/index.tsx b/src/screens/VideoFeed/index.tsx index 6e7bb4139a..38c2e5a89d 100644 --- a/src/screens/VideoFeed/index.tsx +++ b/src/screens/VideoFeed/index.tsx @@ -31,8 +31,8 @@ import { AppBskyFeedPost, AtUri, type ModerationDecision, - RichText as RichTextAPI, } from '@atproto/api' +import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans, useLingui} from '@lingui/react/macro' import { type RouteProp, @@ -58,6 +58,7 @@ import { import {sanitizeDisplayName} from '#/lib/strings/display-names' import {cleanError} from '#/lib/strings/errors' import {sanitizeHandle} from '#/lib/strings/handles' +import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {logger} from '#/logger' import {useA11y} from '#/state/a11y' import { @@ -834,7 +835,7 @@ function Overlay({ : undefined const richText = new RichTextAPI({ text: record?.text || '', - facets: record?.facets, + facets: asSdkFacets(record?.facets), }) const handle = sanitizeHandle(post.author.handle, '@') diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index 9a9a8c09b9..a3b90de32b 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -5,9 +5,9 @@ import { type AppBskyGraphDefs, AtUri, moderateFeedGenerator, - RichText, } from '@atproto/api' import {type AtUriString} from '@atproto/syntax' +import {RichText} from '@bsky.app/sdk/richtext' import {t} from '@lingui/core/macro' import { type InfiniteData, @@ -22,6 +22,7 @@ import { import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' +import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {GCTIME, STALE} from '#/state/queries' import {RQKEY as listQueryKey} from '#/state/queries/list' import {usePreferencesQuery} from '#/state/queries/preferences' @@ -104,7 +105,7 @@ export function hydrateFeedGenerator( const description = new RichText({ text: view.description || '', - facets: (view.descriptionFacets || [])?.slice(), + facets: asSdkFacets((view.descriptionFacets || [])?.slice()), }) if (!view.descriptionFacets) { @@ -145,7 +146,7 @@ export function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo { const description = new RichText({ text: view.description || '', - facets: (view.descriptionFacets || [])?.slice(), + facets: asSdkFacets((view.descriptionFacets || [])?.slice()), }) if (!view.descriptionFacets) { diff --git a/src/state/queries/starter-packs.ts b/src/state/queries/starter-packs.ts index ddf8365dda..6a1aecef0e 100644 --- a/src/state/queries/starter-packs.ts +++ b/src/state/queries/starter-packs.ts @@ -3,11 +3,10 @@ import { AppBskyGraphDefs, type AppBskyGraphGetStarterPack, AppBskyGraphStarterpack, - type AppBskyRichtextFacet, type AtpAgent, AtUri, - RichText, } from '@atproto/api' +import {RichText} from '@bsky.app/sdk/richtext' import { type QueryClient, useMutation, @@ -26,7 +25,8 @@ import { import {invalidateActorStarterPacksQuery} from '#/state/queries/actor-starter-packs' import {STALE} from '#/state/queries/index' import {invalidateListMembersQuery} from '#/state/queries/list-members' -import {useAgent} from '#/state/session' +import {useAgent, useAppviewClient} from '#/state/session' +import {type app} from '#/lexicons' import * as bsky from '#/types/bsky' const RQKEY_ROOT = 'starter-pack' @@ -105,6 +105,11 @@ export function useCreateStarterPackMutation({ }) { const queryClient = useQueryClient() 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 appviewClient = useAppviewClient() return useMutation< {uri: string; cid: string}, @@ -112,10 +117,10 @@ export function useCreateStarterPackMutation({ UseCreateStarterPackMutationParams >({ mutationFn: async ({name, description, feeds, profiles}) => { - let descriptionFacets: AppBskyRichtextFacet.Main[] | undefined + let descriptionFacets: app.bsky.richtext.facet.Main[] | undefined if (description) { const rt = new RichText({text: description}) - await rt.detectFacets(agent) + await rt.detectFacets(appviewClient) descriptionFacets = rt.facets } @@ -167,6 +172,7 @@ export function useEditStarterPackMutation({ }) { const queryClient = useQueryClient() const agent = useAgent() + const appviewClient = useAppviewClient() return useMutation< void, @@ -184,10 +190,10 @@ export function useEditStarterPackMutation({ currentStarterPack, currentListItems, }) => { - let descriptionFacets: AppBskyRichtextFacet.Main[] | undefined + let descriptionFacets: app.bsky.richtext.facet.Main[] | undefined if (description) { const rt = new RichText({text: description}) - await rt.detectFacets(agent) + await rt.detectFacets(appviewClient) descriptionFacets = rt.facets } diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 6c7c2280c2..c2e651556d 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -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] diff --git a/src/view/com/composer/drafts/state/api.ts b/src/view/com/composer/drafts/state/api.ts index b8fddc618f..3b5ff9cdf2 100644 --- a/src/view/com/composer/drafts/state/api.ts +++ b/src/view/com/composer/drafts/state/api.ts @@ -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' diff --git a/src/view/com/composer/select-language/SuggestedLanguage.tsx b/src/view/com/composer/select-language/SuggestedLanguage.tsx index ab731cda22..71a45ec796 100644 --- a/src/view/com/composer/select-language/SuggestedLanguage.tsx +++ b/src/view/com/composer/select-language/SuggestedLanguage.tsx @@ -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' diff --git a/src/view/com/composer/state/composer.ts b/src/view/com/composer/state/composer.ts index fc8b341270..3d2aa45992 100644 --- a/src/view/com/composer/state/composer.ts +++ b/src/view/com/composer/state/composer.ts @@ -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 { diff --git a/src/view/com/composer/text-input/TextInput.tsx b/src/view/com/composer/text-input/TextInput.tsx index 88389ffd56..d230b01545 100644 --- a/src/view/com/composer/text-input/TextInput.tsx +++ b/src/view/com/composer/text-input/TextInput.tsx @@ -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, diff --git a/src/view/com/composer/text-input/TextInput.types.ts b/src/view/com/composer/text-input/TextInput.types.ts index fab2bc32f8..e60432b41e 100644 --- a/src/view/com/composer/text-input/TextInput.types.ts +++ b/src/view/com/composer/text-input/TextInput.types.ts @@ -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 diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index e76d9e3f3d..f75d60f4bd 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -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}) } } diff --git a/src/view/com/composer/text-input/text-input-util.ts b/src/view/com/composer/text-input/text-input-util.ts index 71db8c3238..998d7074d9 100644 --- a/src/view/com/composer/text-input/text-input-util.ts +++ b/src/view/com/composer/text-input/text-input-util.ts @@ -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( diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index dc17bff725..b67133df65 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -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], diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx index eea2453739..2ccf3872dc 100644 --- a/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx @@ -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' diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx index 57f506c5d1..a95b41635b 100644 --- a/src/view/com/posts/PostFeedItem.tsx +++ b/src/view/com/posts/PostFeedItem.tsx @@ -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], ) diff --git a/src/view/screens/DebugMod.tsx b/src/view/screens/DebugMod.tsx index c3fe8fba8e..d92043966c 100644 --- a/src/view/screens/DebugMod.tsx +++ b/src/view/screens/DebugMod.tsx @@ -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' diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index ead74bae94..c796bf9586 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -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(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] }