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.
This commit is contained in:
@@ -1,11 +1,7 @@
|
|||||||
import {useCallback, useEffect, useMemo} from 'react'
|
import {useCallback, useEffect, useMemo} from 'react'
|
||||||
import {type GestureResponderEvent, View} from 'react-native'
|
import {type GestureResponderEvent, View} from 'react-native'
|
||||||
import {
|
import {type AppBskyFeedDefs, type AppBskyGraphDefs, AtUri} from '@atproto/api'
|
||||||
type AppBskyFeedDefs,
|
import {RichText as RichTextApi} from '@bsky.app/sdk/richtext'
|
||||||
type AppBskyGraphDefs,
|
|
||||||
AtUri,
|
|
||||||
RichText as RichTextApi,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import {
|
|||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
AtUri,
|
AtUri,
|
||||||
moderatePost,
|
moderatePost,
|
||||||
RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {getChatInviteCodeFromUrl} from '#/lib/strings/url-helpers'
|
import {getChatInviteCodeFromUrl} from '#/lib/strings/url-helpers'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {unstableCacheProfileView} from '#/state/queries/profile'
|
import {unstableCacheProfileView} from '#/state/queries/profile'
|
||||||
@@ -295,7 +296,7 @@ export function QuoteEmbed({
|
|||||||
return undefined
|
return undefined
|
||||||
const {text, facets} = quote.record
|
const {text, facets} = quote.record
|
||||||
return text.trim()
|
return text.trim()
|
||||||
? new RichTextAPI({text: text, facets: facets})
|
? new RichTextAPI({text: text, facets: asSdkFacets(facets)})
|
||||||
: undefined
|
: undefined
|
||||||
}, [quote.record])
|
}, [quote.record])
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import {
|
|||||||
type AppBskyFeedPost,
|
type AppBskyFeedPost,
|
||||||
type AppBskyFeedThreadgate,
|
type AppBskyFeedThreadgate,
|
||||||
AtUri,
|
AtUri,
|
||||||
type RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {plural} from '@lingui/core/macro'
|
import {plural} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react/macro'
|
import {useLingui} from '@lingui/react/macro'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import {
|
|||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
type AppBskyFeedPost,
|
type AppBskyFeedPost,
|
||||||
type AppBskyFeedThreadgate,
|
type AppBskyFeedThreadgate,
|
||||||
type RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {useLingui} from '@lingui/react/macro'
|
import {useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {type Shadow} from '#/state/cache/post-shadow'
|
import {type Shadow} from '#/state/cache/post-shadow'
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import {
|
|||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
type AppBskyFeedPost,
|
type AppBskyFeedPost,
|
||||||
type AppBskyFeedThreadgate,
|
type AppBskyFeedThreadgate,
|
||||||
type RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
|
|
||||||
import {type Shadow} from '#/state/cache/post-shadow'
|
import {type Shadow} from '#/state/cache/post-shadow'
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import {
|
|||||||
type AppBskyFeedPost,
|
type AppBskyFeedPost,
|
||||||
type AppBskyFeedThreadgate,
|
type AppBskyFeedThreadgate,
|
||||||
AtUri,
|
AtUri,
|
||||||
type RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import {
|
|||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
type AppBskyFeedPost,
|
type AppBskyFeedPost,
|
||||||
type AppBskyFeedThreadgate,
|
type AppBskyFeedThreadgate,
|
||||||
type RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {plural} from '@lingui/core/macro'
|
import {plural} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react/macro'
|
import {useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,8 @@ import {
|
|||||||
View,
|
View,
|
||||||
type ViewStyle,
|
type ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {
|
import {moderateProfile, type ModerationOpts} from '@atproto/api'
|
||||||
moderateProfile,
|
import {RichText as RichTextApi} from '@bsky.app/sdk/richtext'
|
||||||
type ModerationOpts,
|
|
||||||
RichText as RichTextApi,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {useLingui} from '@lingui/react/macro'
|
import {useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {getModerationCauseKey} from '#/lib/moderation'
|
import {getModerationCauseKey} from '#/lib/moderation'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {useMemo} from 'react'
|
import {useMemo} from 'react'
|
||||||
import {type StyleProp, type TextStyle} from 'react-native'
|
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 {toShortUrl} from '#/lib/strings/url-helpers'
|
||||||
import {android, atoms as a, flatten, type TextStyleProp} from '#/alf'
|
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 {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
import {RichTextTag} from '#/components/RichTextTag'
|
import {RichTextTag} from '#/components/RichTextTag'
|
||||||
import {Text, type TextProps} from '#/components/Typography'
|
import {Text, type TextProps} from '#/components/Typography'
|
||||||
|
import {app} from '#/lexicons'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
const WORD_WRAP = {wordWrap: 1}
|
const WORD_WRAP = {wordWrap: 1}
|
||||||
// lifted from facet detection in `RichText` impl, _without_ `gm` flags
|
// lifted from facet detection in `RichText` impl, _without_ `gm` flags
|
||||||
@@ -139,7 +141,7 @@ export function RichText({
|
|||||||
if (
|
if (
|
||||||
mention &&
|
mention &&
|
||||||
(disableMentionFacetValidation ||
|
(disableMentionFacetValidation ||
|
||||||
AppBskyRichtextFacet.validateMention(mention).success) &&
|
bsky.matches(app.bsky.richtext.facet.mention, mention)) &&
|
||||||
!disableLinks
|
!disableLinks
|
||||||
) {
|
) {
|
||||||
els.push(
|
els.push(
|
||||||
@@ -156,7 +158,7 @@ export function RichText({
|
|||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
</ProfileHoverCard>,
|
</ProfileHoverCard>,
|
||||||
)
|
)
|
||||||
} 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)
|
const isValidLink = URL_REGEX.test(link.uri)
|
||||||
if (!isValidLink || disableLinks) {
|
if (!isValidLink || disableLinks) {
|
||||||
els.push(toShortUrl(segment.text))
|
els.push(toShortUrl(segment.text))
|
||||||
@@ -181,7 +183,7 @@ export function RichText({
|
|||||||
!disableLinks &&
|
!disableLinks &&
|
||||||
enableTags &&
|
enableTags &&
|
||||||
tag &&
|
tag &&
|
||||||
AppBskyRichtextFacet.validateTag(tag).success
|
bsky.matches(app.bsky.richtext.facet.tag, tag)
|
||||||
) {
|
) {
|
||||||
els.push(
|
els.push(
|
||||||
<RichTextTag
|
<RichTextTag
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import {useCallback, useEffect, useMemo, useState} from 'react'
|
import {useCallback, useEffect, useMemo, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {type AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api'
|
import {type AppBskyGraphDefs} from '@atproto/api'
|
||||||
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Plural, Trans} from '@lingui/react/macro'
|
import {Plural, Trans} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {isOverMaxGraphemeCount} from '#/lib/strings/helpers'
|
import {isOverMaxGraphemeCount} from '#/lib/strings/helpers'
|
||||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
import {asSdkFacets, richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||||
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
|
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {type ImageMeta} from '#/state/gallery'
|
import {type ImageMeta} from '#/state/gallery'
|
||||||
@@ -15,7 +16,7 @@ import {
|
|||||||
useListCreateMutation,
|
useListCreateMutation,
|
||||||
useListMetadataMutation,
|
useListMetadataMutation,
|
||||||
} from '#/state/queries/list'
|
} from '#/state/queries/list'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient} from '#/state/session'
|
||||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||||
import {EditableUserAvatar} from '#/view/com/util/UserAvatar'
|
import {EditableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {atoms as a, useTheme, web} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
@@ -133,7 +134,11 @@ function DialogInner({
|
|||||||
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
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 control = Dialog.useDialogContext()
|
const control = Dialog.useDialogContext()
|
||||||
const {
|
const {
|
||||||
mutateAsync: createListMutation,
|
mutateAsync: createListMutation,
|
||||||
@@ -163,7 +168,10 @@ function DialogInner({
|
|||||||
|
|
||||||
// We want to be working with a blank state here, so let's get the
|
// We want to be working with a blank state here, so let's get the
|
||||||
// serialized version and turn it back into a RichText
|
// serialized version and turn it back into a RichText
|
||||||
const serialized = richTextToString(new RichTextAPI({text, facets}), false)
|
const serialized = richTextToString(
|
||||||
|
new RichTextAPI({text, facets: asSdkFacets(facets)}),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
const richText = new RichTextAPI({text: serialized})
|
const richText = new RichTextAPI({text: serialized})
|
||||||
richText.detectFacetsWithoutResolution()
|
richText.detectFacetsWithoutResolution()
|
||||||
@@ -224,7 +232,7 @@ function DialogInner({
|
|||||||
{cleanNewlines: true},
|
{cleanNewlines: true},
|
||||||
)
|
)
|
||||||
|
|
||||||
await richText.detectFacets(agent)
|
await richText.detectFacets(appviewClient)
|
||||||
richText = shortenLinks(richText)
|
richText = shortenLinks(richText)
|
||||||
richText = stripInvalidMentions(richText)
|
richText = stripInvalidMentions(richText)
|
||||||
|
|
||||||
@@ -272,7 +280,7 @@ function DialogInner({
|
|||||||
setImageError,
|
setImageError,
|
||||||
activePurpose,
|
activePurpose,
|
||||||
isCurateList,
|
isCurateList,
|
||||||
agent,
|
appviewClient,
|
||||||
_,
|
_,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,14 @@ import {memo, useCallback} from 'react'
|
|||||||
import {Platform} from 'react-native'
|
import {Platform} from 'react-native'
|
||||||
import {type GestureType} from 'react-native-gesture-handler'
|
import {type GestureType} from 'react-native-gesture-handler'
|
||||||
import * as Clipboard from 'expo-clipboard'
|
import * as Clipboard from 'expo-clipboard'
|
||||||
import {
|
import {type ChatBskyConvoDefs, type ModerationOpts} from '@atproto/api'
|
||||||
type ChatBskyConvoDefs,
|
import {RichText} from '@bsky.app/sdk/richtext'
|
||||||
type ModerationOpts,
|
|
||||||
RichText,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {plural} from '@lingui/core/macro'
|
import {plural} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react/macro'
|
import {useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {EMOJI_REACTION_LIMIT} from '#/lib/constants'
|
import {EMOJI_REACTION_LIMIT} from '#/lib/constants'
|
||||||
import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate'
|
import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate'
|
||||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
import {asSdkFacets, richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||||
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
|
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {useConvoActive} from '#/state/messages/convo'
|
import {useConvoActive} from '#/state/messages/convo'
|
||||||
import {useLanguagePrefs} from '#/state/preferences'
|
import {useLanguagePrefs} from '#/state/preferences'
|
||||||
@@ -88,7 +85,7 @@ export let MessageContextMenu = ({
|
|||||||
const str = richTextToString(
|
const str = richTextToString(
|
||||||
new RichText({
|
new RichText({
|
||||||
text: message.text,
|
text: message.text,
|
||||||
facets: message.facets,
|
facets: asSdkFacets(message.facets),
|
||||||
}),
|
}),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ import {
|
|||||||
ChatBskyConvoDefs,
|
ChatBskyConvoDefs,
|
||||||
ChatBskyEmbedJoinLink,
|
ChatBskyEmbedJoinLink,
|
||||||
moderateProfile,
|
moderateProfile,
|
||||||
RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {plural} from '@lingui/core/macro'
|
import {plural} from '@lingui/core/macro'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
@@ -37,6 +37,7 @@ import {useQueryClient} from '@tanstack/react-query'
|
|||||||
import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted'
|
import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted'
|
||||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
|
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {type Shadow} from '#/state/cache/types'
|
import {type Shadow} from '#/state/cache/types'
|
||||||
import {type ConvoItem} from '#/state/messages/convo/types'
|
import {type ConvoItem} from '#/state/messages/convo/types'
|
||||||
@@ -245,7 +246,10 @@ let MessageItem = ({
|
|||||||
? t.palette.primary_300
|
? t.palette.primary_300
|
||||||
: t.palette.primary_100
|
: t.palette.primary_100
|
||||||
|
|
||||||
const rt = new RichTextAPI({text: message.text, facets: message.facets})
|
const rt = new RichTextAPI({
|
||||||
|
text: message.text,
|
||||||
|
facets: asSdkFacets(message.facets),
|
||||||
|
})
|
||||||
|
|
||||||
const isEmojiOnly = isOnlyEmoji(message.text)
|
const isEmojiOnly = isOnlyEmoji(message.text)
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import {useEffect, useState} from 'react'
|
import {useEffect, useState} from 'react'
|
||||||
import {RichText as RichTextAPI} from '@atproto/api'
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
|
|
||||||
import {useAgent} from '#/state/session'
|
import {useAppviewClient} from '#/state/session'
|
||||||
|
|
||||||
export function useRichText(text: string): [RichTextAPI, boolean] {
|
export function useRichText(text: string): [RichTextAPI, boolean] {
|
||||||
const [prevText, setPrevText] = useState(text)
|
const [prevText, setPrevText] = useState(text)
|
||||||
const [rawRT, setRawRT] = useState(() => new RichTextAPI({text}))
|
const [rawRT, setRawRT] = useState(() => new RichTextAPI({text}))
|
||||||
const [resolvedRT, setResolvedRT] = useState<RichTextAPI | null>(null)
|
const [resolvedRT, setResolvedRT] = useState<RichTextAPI | null>(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) {
|
if (text !== prevText) {
|
||||||
setPrevText(text)
|
setPrevText(text)
|
||||||
setRawRT(new RichTextAPI({text}))
|
setRawRT(new RichTextAPI({text}))
|
||||||
@@ -19,7 +24,7 @@ export function useRichText(text: string): [RichTextAPI, boolean] {
|
|||||||
async function resolveRTFacets() {
|
async function resolveRTFacets() {
|
||||||
// new each time
|
// new each time
|
||||||
const resolvedRT = new RichTextAPI({text})
|
const resolvedRT = new RichTextAPI({text})
|
||||||
await resolvedRT.detectFacets(agent)
|
await resolvedRT.detectFacets(client)
|
||||||
if (!ignore) {
|
if (!ignore) {
|
||||||
setResolvedRT(resolvedRT)
|
setResolvedRT(resolvedRT)
|
||||||
}
|
}
|
||||||
@@ -28,7 +33,7 @@ export function useRichText(text: string): [RichTextAPI, boolean] {
|
|||||||
return () => {
|
return () => {
|
||||||
ignore = true
|
ignore = true
|
||||||
}
|
}
|
||||||
}, [text, agent])
|
}, [text, client])
|
||||||
const isResolving = resolvedRT === null
|
const isResolving = resolvedRT === null
|
||||||
return [resolvedRT ?? rawRT, isResolving]
|
return [resolvedRT ?? rawRT, isResolving]
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-4
@@ -13,9 +13,10 @@ import {
|
|||||||
type ComAtprotoLabelDefs,
|
type ComAtprotoLabelDefs,
|
||||||
type ComAtprotoRepoApplyWrites,
|
type ComAtprotoRepoApplyWrites,
|
||||||
type ComAtprotoRepoStrongRef,
|
type ComAtprotoRepoStrongRef,
|
||||||
RichText,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {TID} from '@atproto/common-web'
|
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 {t} from '@lingui/core/macro'
|
||||||
import {type QueryClient} from '@tanstack/react-query'
|
import {type QueryClient} from '@tanstack/react-query'
|
||||||
import {sha256} from 'js-sha256'
|
import {sha256} from 'js-sha256'
|
||||||
@@ -51,6 +52,12 @@ interface PostOpts {
|
|||||||
replyTo?: string
|
replyTo?: string
|
||||||
onStateChange?: (state: string) => void
|
onStateChange?: (state: string) => void
|
||||||
langs?: string[]
|
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(
|
export async function post(
|
||||||
@@ -87,7 +94,7 @@ export async function post(
|
|||||||
const draft = thread.posts[i]
|
const draft = thread.posts[i]
|
||||||
|
|
||||||
// Not awaited to avoid waterfalls.
|
// Not awaited to avoid waterfalls.
|
||||||
const rtPromise = resolveRT(agent, draft.richtext)
|
const rtPromise = resolveRT(opts.appviewClient, draft.richtext)
|
||||||
const embedPromise = resolveEmbed(
|
const embedPromise = resolveEmbed(
|
||||||
agent,
|
agent,
|
||||||
queryClient,
|
queryClient,
|
||||||
@@ -196,14 +203,14 @@ export async function post(
|
|||||||
return {uris}
|
return {uris}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveRT(agent: AtpAgent, richtext: RichText) {
|
async function resolveRT(appviewClient: Client, richtext: RichText) {
|
||||||
const trimmedText = richtext.text
|
const trimmedText = richtext.text
|
||||||
// Trim leading whitespace-only lines (but don't break ASCII art).
|
// Trim leading whitespace-only lines (but don't break ASCII art).
|
||||||
.replace(/^(\s*\n)+/, '')
|
.replace(/^(\s*\n)+/, '')
|
||||||
// Trim any trailing whitespace.
|
// Trim any trailing whitespace.
|
||||||
.trimEnd()
|
.trimEnd()
|
||||||
let rt = new RichText({text: trimmedText}, {cleanNewlines: true})
|
let rt = new RichText({text: trimmedText}, {cleanNewlines: true})
|
||||||
await rt.detectFacets(agent)
|
await rt.detectFacets(appviewClient)
|
||||||
|
|
||||||
rt = shortenLinks(rt)
|
rt = shortenLinks(rt)
|
||||||
rt = stripInvalidMentions(rt)
|
rt = stripInvalidMentions(rt)
|
||||||
|
|||||||
@@ -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 {countGraphemes} from 'unicode-segmenter/grapheme'
|
||||||
|
|
||||||
import {shortenLinks} from './rich-text-manip'
|
import {shortenLinks} from './rich-text-manip'
|
||||||
|
|||||||
@@ -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'
|
import {linkRequiresWarning} from './url-helpers'
|
||||||
|
|
||||||
export function richTextToString(rt: RichText, loose: boolean): string {
|
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()) {
|
for (const segment of rt.segments()) {
|
||||||
const link = segment.link
|
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 href = link.uri
|
||||||
const text = segment.text
|
const text = segment.text
|
||||||
|
|
||||||
@@ -28,3 +30,26 @@ export function richTextToString(rt: RichText, loose: boolean): string {
|
|||||||
|
|
||||||
return result
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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'
|
import {toShortUrl} from './url-helpers'
|
||||||
|
|
||||||
export function shortenLinks(rt: RichText): RichText {
|
export function shortenLinks(rt: RichText): RichText {
|
||||||
@@ -10,7 +12,9 @@ export function shortenLinks(rt: RichText): RichText {
|
|||||||
// enumerate the link facets
|
// enumerate the link facets
|
||||||
if (rt.facets) {
|
if (rt.facets) {
|
||||||
for (const facet of 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) {
|
if (!isLink) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -40,7 +44,9 @@ export function stripInvalidMentions(rt: RichText): RichText {
|
|||||||
rt = rt.clone()
|
rt = rt.clone()
|
||||||
if (rt.facets) {
|
if (rt.facets) {
|
||||||
rt.facets = rt.facets?.filter(facet => {
|
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) {
|
if (mention && !mention.did) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import {useCallback, useEffect, useMemo, useState} from 'react'
|
import {useCallback, useEffect, useMemo, useState} from 'react'
|
||||||
import {LayoutAnimation, View} from 'react-native'
|
import {LayoutAnimation, View} from 'react-native'
|
||||||
import {
|
import {AppBskyFeedPost, AtUri, moderatePost} from '@atproto/api'
|
||||||
AppBskyFeedPost,
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
AppBskyRichtextFacet,
|
|
||||||
AtUri,
|
|
||||||
moderatePost,
|
|
||||||
RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native'
|
import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native'
|
||||||
|
|
||||||
@@ -16,6 +11,7 @@ import {
|
|||||||
type CommonNavigatorParams,
|
type CommonNavigatorParams,
|
||||||
type NavigationProp,
|
type NavigationProp,
|
||||||
} from '#/lib/routes/types'
|
} from '#/lib/routes/types'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {
|
import {
|
||||||
convertBskyAppUrlIfNeeded,
|
convertBskyAppUrlIfNeeded,
|
||||||
getChatInviteCodeFromUrl,
|
getChatInviteCodeFromUrl,
|
||||||
@@ -36,6 +32,7 @@ import {ContentHider} from '#/components/moderation/ContentHider'
|
|||||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import {app} from '#/lexicons'
|
||||||
import * as bsky from '#/types/bsky'
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,7 +103,7 @@ export function useExtractEmbedFromFacets(
|
|||||||
for (const facet of rt.facets ?? []) {
|
for (const facet of rt.facets ?? []) {
|
||||||
for (const feature of facet.features) {
|
for (const feature of facet.features) {
|
||||||
if (
|
if (
|
||||||
AppBskyRichtextFacet.isLink(feature) &&
|
bsky.isType(app.bsky.richtext.facet.link, feature) &&
|
||||||
(isBskyPostUrl(feature.uri) || isBskyChatInviteUrl(feature.uri))
|
(isBskyPostUrl(feature.uri) || isBskyChatInviteUrl(feature.uri))
|
||||||
) {
|
) {
|
||||||
uriFromFacet = feature.uri
|
uriFromFacet = feature.uri
|
||||||
@@ -176,7 +173,7 @@ function MessageInputPostEmbed({
|
|||||||
return {
|
return {
|
||||||
rt: new RichTextAPI({
|
rt: new RichTextAPI({
|
||||||
text: post.record.text,
|
text: post.record.text,
|
||||||
facets: post.record.facets,
|
facets: asSdkFacets(post.record.facets),
|
||||||
}),
|
}),
|
||||||
record: post.record,
|
record: post.record,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,13 +25,12 @@ import {scheduleOnRN} from 'react-native-worklets'
|
|||||||
import {
|
import {
|
||||||
type $Typed,
|
type $Typed,
|
||||||
type AppBskyEmbedRecord,
|
type AppBskyEmbedRecord,
|
||||||
AppBskyRichtextFacet,
|
|
||||||
ChatBskyConvoDefs,
|
ChatBskyConvoDefs,
|
||||||
type ChatBskyEmbedJoinLink,
|
type ChatBskyEmbedJoinLink,
|
||||||
ChatBskyGroupDefs,
|
ChatBskyGroupDefs,
|
||||||
RichText,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {useScrollEdgeEffectRef} from '@bsky.app/expo-scroll-edge-effect'
|
import {useScrollEdgeEffectRef} from '@bsky.app/expo-scroll-edge-effect'
|
||||||
|
import {RichText} from '@bsky.app/sdk/richtext'
|
||||||
|
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
import {mergeRefs} from '#/lib/merge-refs'
|
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 {useGetJoinLinkPreview} from '#/state/queries/join-links'
|
||||||
import {useGetPost} from '#/state/queries/post'
|
import {useGetPost} from '#/state/queries/post'
|
||||||
import {createEmbedViewRecordFromPost} from '#/state/queries/postgate/util'
|
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 {List, type ListMethods} from '#/view/com/util/List'
|
||||||
import {MessageComposer} from '#/screens/Messages/components/MessageComposer'
|
import {MessageComposer} from '#/screens/Messages/components/MessageComposer'
|
||||||
import {MessageListError} from '#/screens/Messages/components/MessageListError'
|
import {MessageListError} from '#/screens/Messages/components/MessageListError'
|
||||||
@@ -71,6 +70,8 @@ import {Loader} from '#/components/Loader'
|
|||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {IS_ANDROID, IS_NATIVE, IS_WEB} from '#/env'
|
import {IS_ANDROID, IS_NATIVE, IS_WEB} from '#/env'
|
||||||
|
import {app} from '#/lexicons'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
import {ChatStatusInfo} from './ChatStatusInfo'
|
import {ChatStatusInfo} from './ChatStatusInfo'
|
||||||
import {groupSystemMessages, type RenderItem} from './groupSystemMessages'
|
import {groupSystemMessages, type RenderItem} from './groupSystemMessages'
|
||||||
import {InviteLinkDialogProvider} from './InviteLinkDialogProvider'
|
import {InviteLinkDialogProvider} from './InviteLinkDialogProvider'
|
||||||
@@ -144,7 +145,11 @@ export function MessagesList({
|
|||||||
}) {
|
}) {
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const convoState = useConvoActive()
|
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 {hasSession, currentAccount} = useSession()
|
||||||
const getPost = useGetPost()
|
const getPost = useGetPost()
|
||||||
const getJoinLinkPreview = useGetJoinLinkPreview()
|
const getJoinLinkPreview = useGetJoinLinkPreview()
|
||||||
@@ -550,7 +555,8 @@ export function MessagesList({
|
|||||||
const linkFacet = rt.facets?.find(facet =>
|
const linkFacet = rt.facets?.find(facet =>
|
||||||
facet.features.find(
|
facet.features.find(
|
||||||
feature =>
|
feature =>
|
||||||
AppBskyRichtextFacet.isLink(feature) && predicate(feature.uri),
|
bsky.isType(app.bsky.richtext.facet.link, feature) &&
|
||||||
|
predicate(feature.uri),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (linkFacet) {
|
if (linkFacet) {
|
||||||
@@ -617,7 +623,7 @@ export function MessagesList({
|
|||||||
replyTo = {messageId: reply.id}
|
replyTo = {messageId: reply.id}
|
||||||
}
|
}
|
||||||
|
|
||||||
await rt.detectFacets(agent)
|
await rt.detectFacets(appviewClient)
|
||||||
|
|
||||||
rt = shortenLinks(rt)
|
rt = shortenLinks(rt)
|
||||||
rt = stripInvalidMentions(rt)
|
rt = stripInvalidMentions(rt)
|
||||||
@@ -667,7 +673,7 @@ export function MessagesList({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
agent,
|
appviewClient,
|
||||||
convoState,
|
convoState,
|
||||||
getPost,
|
getPost,
|
||||||
getJoinLinkPreview,
|
getJoinLinkPreview,
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import {
|
|||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
type AppBskyFeedThreadgate,
|
type AppBskyFeedThreadgate,
|
||||||
AtUri,
|
AtUri,
|
||||||
RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
@@ -14,6 +14,7 @@ import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
|||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {niceDate} from '#/lib/strings/time'
|
import {niceDate} from '#/lib/strings/time'
|
||||||
import {
|
import {
|
||||||
POST_TOMBSTONE,
|
POST_TOMBSTONE,
|
||||||
@@ -199,7 +200,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
() =>
|
() =>
|
||||||
new RichTextAPI({
|
new RichTextAPI({
|
||||||
text: record.text,
|
text: record.text,
|
||||||
facets: record.facets,
|
facets: asSdkFacets(record.facets),
|
||||||
}),
|
}),
|
||||||
[record],
|
[record],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ import {
|
|||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
type AppBskyFeedThreadgate,
|
type AppBskyFeedThreadgate,
|
||||||
AtUri,
|
AtUri,
|
||||||
RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {MAX_POST_LINES} from '#/lib/constants'
|
import {MAX_POST_LINES} from '#/lib/constants'
|
||||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {countLines} from '#/lib/strings/helpers'
|
import {countLines} from '#/lib/strings/helpers'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {
|
import {
|
||||||
POST_TOMBSTONE,
|
POST_TOMBSTONE,
|
||||||
type Shadow,
|
type Shadow,
|
||||||
@@ -211,7 +212,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
|
|||||||
() =>
|
() =>
|
||||||
new RichTextAPI({
|
new RichTextAPI({
|
||||||
text: record.text,
|
text: record.text,
|
||||||
facets: record.facets,
|
facets: asSdkFacets(record.facets),
|
||||||
}),
|
}),
|
||||||
[record],
|
[record],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ import {
|
|||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
type AppBskyFeedThreadgate,
|
type AppBskyFeedThreadgate,
|
||||||
AtUri,
|
AtUri,
|
||||||
RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {MAX_POST_LINES} from '#/lib/constants'
|
import {MAX_POST_LINES} from '#/lib/constants'
|
||||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {countLines} from '#/lib/strings/helpers'
|
import {countLines} from '#/lib/strings/helpers'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {
|
import {
|
||||||
POST_TOMBSTONE,
|
POST_TOMBSTONE,
|
||||||
type Shadow,
|
type Shadow,
|
||||||
@@ -271,7 +272,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
|
|||||||
() =>
|
() =>
|
||||||
new RichTextAPI({
|
new RichTextAPI({
|
||||||
text: record.text,
|
text: record.text,
|
||||||
facets: record.facets,
|
facets: asSdkFacets(record.facets),
|
||||||
}),
|
}),
|
||||||
[record],
|
[record],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import {
|
|||||||
type AppBskyLabelerDefs,
|
type AppBskyLabelerDefs,
|
||||||
moderateProfile,
|
moderateProfile,
|
||||||
type ModerationOpts,
|
type ModerationOpts,
|
||||||
type RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {msg, plural} from '@lingui/core/macro'
|
import {msg, plural} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Plural, Trans} from '@lingui/react/macro'
|
import {Plural, Trans} from '@lingui/react/macro'
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import {
|
|||||||
moderateProfile,
|
moderateProfile,
|
||||||
type ModerationDecision,
|
type ModerationDecision,
|
||||||
type ModerationOpts,
|
type ModerationOpts,
|
||||||
type RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import {
|
|||||||
type AppBskyLabelerDefs,
|
type AppBskyLabelerDefs,
|
||||||
moderateProfile,
|
moderateProfile,
|
||||||
type ModerationOpts,
|
type ModerationOpts,
|
||||||
type RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {useIsFocused} from '@react-navigation/native'
|
import {useIsFocused} from '@react-navigation/native'
|
||||||
|
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import {useMemo} from 'react'
|
import {useMemo} from 'react'
|
||||||
import {View} from 'react-native'
|
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 {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {useHaptics} from '#/lib/haptics'
|
import {useHaptics} from '#/lib/haptics'
|
||||||
import {makeListLink} from '#/lib/routes/links'
|
import {makeListLink} from '#/lib/routes/links'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list'
|
import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list'
|
||||||
import {
|
import {
|
||||||
@@ -128,7 +130,7 @@ export function Header({
|
|||||||
list.description
|
list.description
|
||||||
? new RichTextAPI({
|
? new RichTextAPI({
|
||||||
text: list.description,
|
text: list.description,
|
||||||
facets: list.descriptionFacets,
|
facets: asSdkFacets(list.descriptionFacets),
|
||||||
})
|
})
|
||||||
: undefined,
|
: undefined,
|
||||||
[list],
|
[list],
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import {useMemo} from 'react'
|
import {useMemo} from 'react'
|
||||||
import {Pressable, View} from 'react-native'
|
import {Pressable, View} from 'react-native'
|
||||||
import {Image} from 'expo-image'
|
import {Image} from 'expo-image'
|
||||||
import {
|
import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api'
|
||||||
type AppBskyUnspeccedDefs,
|
import {RichText as RichTextApi} from '@bsky.app/sdk/richtext'
|
||||||
moderateProfile,
|
|
||||||
RichText as RichTextApi,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
AppBskyGraphStarterpack,
|
AppBskyGraphStarterpack,
|
||||||
AtUri,
|
AtUri,
|
||||||
type ModerationOpts,
|
type ModerationOpts,
|
||||||
RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Plural, Trans} from '@lingui/react/macro'
|
import {Plural, Trans} from '@lingui/react/macro'
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
type NavigationProp,
|
type NavigationProp,
|
||||||
} from '#/lib/routes/types'
|
} from '#/lib/routes/types'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {getStarterPackOgCard} from '#/lib/strings/starter-pack'
|
import {getStarterPackOgCard} from '#/lib/strings/starter-pack'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {updateProfileShadow} from '#/state/cache/profile-shadow'
|
import {updateProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
@@ -418,7 +419,7 @@ function Header({
|
|||||||
const richText = record.description
|
const richText = record.description
|
||||||
? new RichTextAPI({
|
? new RichTextAPI({
|
||||||
text: record.description,
|
text: record.description,
|
||||||
facets: record.descriptionFacets,
|
facets: asSdkFacets(record.descriptionFacets),
|
||||||
})
|
})
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ import {
|
|||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
AtUri,
|
AtUri,
|
||||||
type ModerationDecision,
|
type ModerationDecision,
|
||||||
RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {
|
import {
|
||||||
type RouteProp,
|
type RouteProp,
|
||||||
@@ -58,6 +58,7 @@ import {
|
|||||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useA11y} from '#/state/a11y'
|
import {useA11y} from '#/state/a11y'
|
||||||
import {
|
import {
|
||||||
@@ -834,7 +835,7 @@ function Overlay({
|
|||||||
: undefined
|
: undefined
|
||||||
const richText = new RichTextAPI({
|
const richText = new RichTextAPI({
|
||||||
text: record?.text || '',
|
text: record?.text || '',
|
||||||
facets: record?.facets,
|
facets: asSdkFacets(record?.facets),
|
||||||
})
|
})
|
||||||
const handle = sanitizeHandle(post.author.handle, '@')
|
const handle = sanitizeHandle(post.author.handle, '@')
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import {
|
|||||||
type AppBskyGraphDefs,
|
type AppBskyGraphDefs,
|
||||||
AtUri,
|
AtUri,
|
||||||
moderateFeedGenerator,
|
moderateFeedGenerator,
|
||||||
RichText,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {type AtUriString} from '@atproto/syntax'
|
import {type AtUriString} from '@atproto/syntax'
|
||||||
|
import {RichText} from '@bsky.app/sdk/richtext'
|
||||||
import {t} from '@lingui/core/macro'
|
import {t} from '@lingui/core/macro'
|
||||||
import {
|
import {
|
||||||
type InfiniteData,
|
type InfiniteData,
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants'
|
import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants'
|
||||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {GCTIME, STALE} from '#/state/queries'
|
import {GCTIME, STALE} from '#/state/queries'
|
||||||
import {RQKEY as listQueryKey} from '#/state/queries/list'
|
import {RQKEY as listQueryKey} from '#/state/queries/list'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
@@ -104,7 +105,7 @@ export function hydrateFeedGenerator(
|
|||||||
|
|
||||||
const description = new RichText({
|
const description = new RichText({
|
||||||
text: view.description || '',
|
text: view.description || '',
|
||||||
facets: (view.descriptionFacets || [])?.slice(),
|
facets: asSdkFacets((view.descriptionFacets || [])?.slice()),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!view.descriptionFacets) {
|
if (!view.descriptionFacets) {
|
||||||
@@ -145,7 +146,7 @@ export function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo {
|
|||||||
|
|
||||||
const description = new RichText({
|
const description = new RichText({
|
||||||
text: view.description || '',
|
text: view.description || '',
|
||||||
facets: (view.descriptionFacets || [])?.slice(),
|
facets: asSdkFacets((view.descriptionFacets || [])?.slice()),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!view.descriptionFacets) {
|
if (!view.descriptionFacets) {
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ import {
|
|||||||
AppBskyGraphDefs,
|
AppBskyGraphDefs,
|
||||||
type AppBskyGraphGetStarterPack,
|
type AppBskyGraphGetStarterPack,
|
||||||
AppBskyGraphStarterpack,
|
AppBskyGraphStarterpack,
|
||||||
type AppBskyRichtextFacet,
|
|
||||||
type AtpAgent,
|
type AtpAgent,
|
||||||
AtUri,
|
AtUri,
|
||||||
RichText,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText} from '@bsky.app/sdk/richtext'
|
||||||
import {
|
import {
|
||||||
type QueryClient,
|
type QueryClient,
|
||||||
useMutation,
|
useMutation,
|
||||||
@@ -26,7 +25,8 @@ import {
|
|||||||
import {invalidateActorStarterPacksQuery} from '#/state/queries/actor-starter-packs'
|
import {invalidateActorStarterPacksQuery} from '#/state/queries/actor-starter-packs'
|
||||||
import {STALE} from '#/state/queries/index'
|
import {STALE} from '#/state/queries/index'
|
||||||
import {invalidateListMembersQuery} from '#/state/queries/list-members'
|
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'
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
const RQKEY_ROOT = 'starter-pack'
|
const RQKEY_ROOT = 'starter-pack'
|
||||||
@@ -105,6 +105,11 @@ export function useCreateStarterPackMutation({
|
|||||||
}) {
|
}) {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
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<
|
return useMutation<
|
||||||
{uri: string; cid: string},
|
{uri: string; cid: string},
|
||||||
@@ -112,10 +117,10 @@ export function useCreateStarterPackMutation({
|
|||||||
UseCreateStarterPackMutationParams
|
UseCreateStarterPackMutationParams
|
||||||
>({
|
>({
|
||||||
mutationFn: async ({name, description, feeds, profiles}) => {
|
mutationFn: async ({name, description, feeds, profiles}) => {
|
||||||
let descriptionFacets: AppBskyRichtextFacet.Main[] | undefined
|
let descriptionFacets: app.bsky.richtext.facet.Main[] | undefined
|
||||||
if (description) {
|
if (description) {
|
||||||
const rt = new RichText({text: description})
|
const rt = new RichText({text: description})
|
||||||
await rt.detectFacets(agent)
|
await rt.detectFacets(appviewClient)
|
||||||
descriptionFacets = rt.facets
|
descriptionFacets = rt.facets
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +172,7 @@ export function useEditStarterPackMutation({
|
|||||||
}) {
|
}) {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
|
const appviewClient = useAppviewClient()
|
||||||
|
|
||||||
return useMutation<
|
return useMutation<
|
||||||
void,
|
void,
|
||||||
@@ -184,10 +190,10 @@ export function useEditStarterPackMutation({
|
|||||||
currentStarterPack,
|
currentStarterPack,
|
||||||
currentListItems,
|
currentListItems,
|
||||||
}) => {
|
}) => {
|
||||||
let descriptionFacets: AppBskyRichtextFacet.Main[] | undefined
|
let descriptionFacets: app.bsky.richtext.facet.Main[] | undefined
|
||||||
if (description) {
|
if (description) {
|
||||||
const rt = new RichText({text: description})
|
const rt = new RichText({text: description})
|
||||||
await rt.detectFacets(agent)
|
await rt.detectFacets(appviewClient)
|
||||||
descriptionFacets = rt.facets
|
descriptionFacets = rt.facets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ import {
|
|||||||
AppBskyUnspeccedDefs,
|
AppBskyUnspeccedDefs,
|
||||||
AtUri,
|
AtUri,
|
||||||
ChatBskyGroupDefs,
|
ChatBskyGroupDefs,
|
||||||
type RichText,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {type Client} from '@atproto/lex'
|
import {type Client} from '@atproto/lex'
|
||||||
import {type AtUriString} from '@atproto/syntax'
|
import {type AtUriString} from '@atproto/syntax'
|
||||||
|
import {type RichText} from '@bsky.app/sdk/richtext'
|
||||||
import {plural} from '@lingui/core/macro'
|
import {plural} from '@lingui/core/macro'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
@@ -1083,6 +1083,7 @@ export const ComposePost = ({
|
|||||||
replyTo: replyTo?.uri,
|
replyTo: replyTo?.uri,
|
||||||
onStateChange: setPublishingStage,
|
onStateChange: setPublishingStage,
|
||||||
langs: currentLanguages,
|
langs: currentLanguages,
|
||||||
|
appviewClient: client,
|
||||||
})
|
})
|
||||||
).uris[0]
|
).uris[0]
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Type converters for Draft API - convert between ComposerState and server Draft types.
|
* 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 {nanoid} from 'nanoid/non-secure'
|
||||||
|
|
||||||
import {resolveLink} from '#/lib/api/resolve'
|
import {resolveLink} from '#/lib/api/resolve'
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {useEffect, useMemo, useRef, useState} from 'react'
|
import {useEffect, useMemo, useRef, useState} from 'react'
|
||||||
import {Platform, Text as RNText, View} from 'react-native'
|
import {Platform, Text as RNText, View} from 'react-native'
|
||||||
import {RichText} from '@atproto/api'
|
|
||||||
import {parseLanguageString} from '@atproto/syntax'
|
import {parseLanguageString} from '@atproto/syntax'
|
||||||
import {
|
import {
|
||||||
guessLanguageAsync,
|
guessLanguageAsync,
|
||||||
type LanguageResult,
|
type LanguageResult,
|
||||||
} from '@bsky.app/expo-guess-language'
|
} from '@bsky.app/expo-guess-language'
|
||||||
|
import {RichText} from '@bsky.app/sdk/richtext'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import debounce from 'lodash.debounce'
|
import debounce from 'lodash.debounce'
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import {
|
|||||||
type AppBskyActorDefs,
|
type AppBskyActorDefs,
|
||||||
type AppBskyDraftDefs,
|
type AppBskyDraftDefs,
|
||||||
type AppBskyFeedPostgate,
|
type AppBskyFeedPostgate,
|
||||||
AppBskyRichtextFacet,
|
|
||||||
RichText,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText} from '@bsky.app/sdk/richtext'
|
||||||
import {nanoid} from 'nanoid/non-secure'
|
import {nanoid} from 'nanoid/non-secure'
|
||||||
|
|
||||||
import {type VideoTelemetry} from '#/lib/media/video/telemetry'
|
import {type VideoTelemetry} from '#/lib/media/video/telemetry'
|
||||||
@@ -28,6 +27,8 @@ import {
|
|||||||
suggestLinkCardUri,
|
suggestLinkCardUri,
|
||||||
} from '#/view/com/composer/text-input/text-input-util'
|
} from '#/view/com/composer/text-input/text-input-util'
|
||||||
import {type Gif} from '#/features/gifPicker/types'
|
import {type Gif} from '#/features/gifPicker/types'
|
||||||
|
import {app} from '#/lexicons'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
import {
|
import {
|
||||||
createVideoState,
|
createVideoState,
|
||||||
type VideoAction,
|
type VideoAction,
|
||||||
@@ -677,7 +678,7 @@ export function createComposerState({
|
|||||||
if (initRichText.facets) {
|
if (initRichText.facets) {
|
||||||
for (const facet of initRichText.facets) {
|
for (const facet of initRichText.facets) {
|
||||||
for (const feature of facet.features) {
|
for (const feature of facet.features) {
|
||||||
if (AppBskyRichtextFacet.isLink(feature)) {
|
if (bsky.isType(app.bsky.richtext.facet.link, feature)) {
|
||||||
if (isBskyPostUrl(feature.uri)) {
|
if (isBskyPostUrl(feature.uri)) {
|
||||||
detectedPostUris.set(feature.uri, {facet, rt: initRichText})
|
detectedPostUris.set(feature.uri, {facet, rt: initRichText})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {type PasteEventPayload, TextInputWrapper} from 'expo-paste-input'
|
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 {useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {IMAGE_SIZE_CONFIG_POSTS} from '#/lib/constants'
|
import {IMAGE_SIZE_CONFIG_POSTS} from '#/lib/constants'
|
||||||
@@ -27,6 +27,8 @@ import {
|
|||||||
import {atoms as a, useAlf} from '#/alf'
|
import {atoms as a, useAlf} from '#/alf'
|
||||||
import {normalizeTextStyles} from '#/alf/typography'
|
import {normalizeTextStyles} from '#/alf/typography'
|
||||||
import {IS_ANDROID} from '#/env'
|
import {IS_ANDROID} from '#/env'
|
||||||
|
import {app} from '#/lexicons'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
import {Autocomplete} from './mobile/Autocomplete'
|
import {Autocomplete} from './mobile/Autocomplete'
|
||||||
import {type TextInputProps} from './TextInput.types'
|
import {type TextInputProps} from './TextInput.types'
|
||||||
|
|
||||||
@@ -88,7 +90,7 @@ export function TextInput({
|
|||||||
if (newRt.facets) {
|
if (newRt.facets) {
|
||||||
for (const facet of newRt.facets) {
|
for (const facet of newRt.facets) {
|
||||||
for (const feature of facet.features) {
|
for (const feature of facet.features) {
|
||||||
if (AppBskyRichtextFacet.isLink(feature)) {
|
if (bsky.isType(app.bsky.richtext.facet.link, feature)) {
|
||||||
if (isUriImage(feature.uri)) {
|
if (isUriImage(feature.uri)) {
|
||||||
const res = await downloadAndResize({
|
const res = await downloadAndResize({
|
||||||
uri: feature.uri,
|
uri: feature.uri,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {type TextInput} from 'react-native'
|
import {type TextInput} from 'react-native'
|
||||||
import {type RichText} from '@atproto/api'
|
import {type RichText} from '@bsky.app/sdk/richtext'
|
||||||
|
|
||||||
export type TextInputRef = {
|
export type TextInputRef = {
|
||||||
focus: () => void
|
focus: () => void
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import {StyleSheet, View} from 'react-native'
|
import {StyleSheet, View} from 'react-native'
|
||||||
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
|
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 {Trans} from '@lingui/react/macro'
|
||||||
import {getSchema} from '@tiptap/core'
|
import {getSchema} from '@tiptap/core'
|
||||||
import {Document} from '@tiptap/extension-document'
|
import {Document} from '@tiptap/extension-document'
|
||||||
@@ -40,6 +40,8 @@ import {normalizeTextStyles} from '#/alf/typography'
|
|||||||
import {type Emoji} from '#/components/EmojiPicker'
|
import {type Emoji} from '#/components/EmojiPicker'
|
||||||
import {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import {app} from '#/lexicons'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
import {type TextInputProps} from './TextInput.types'
|
import {type TextInputProps} from './TextInput.types'
|
||||||
import {type AutocompleteRef, createSuggestion} from './web/Autocomplete'
|
import {type AutocompleteRef, createSuggestion} from './web/Autocomplete'
|
||||||
import {LinkDecorator} from './web/LinkDecorator'
|
import {LinkDecorator} from './web/LinkDecorator'
|
||||||
@@ -265,7 +267,7 @@ export function TextInput({
|
|||||||
if (newRt.facets) {
|
if (newRt.facets) {
|
||||||
for (const facet of newRt.facets) {
|
for (const facet of newRt.facets) {
|
||||||
for (const feature of facet.features) {
|
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})
|
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 = {
|
export type LinkFacetMatch = {
|
||||||
rt: RichText
|
rt: RichText
|
||||||
facet: AppBskyRichtextFacet.Main
|
facet: app.bsky.richtext.facet.Main
|
||||||
}
|
}
|
||||||
|
|
||||||
export function suggestLinkCardUri(
|
export function suggestLinkCardUri(
|
||||||
|
|||||||
@@ -6,14 +6,15 @@ import {
|
|||||||
AtUri,
|
AtUri,
|
||||||
moderatePost,
|
moderatePost,
|
||||||
type ModerationDecision,
|
type ModerationDecision,
|
||||||
RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {MAX_POST_LINES} from '#/lib/constants'
|
import {MAX_POST_LINES} from '#/lib/constants'
|
||||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {countLines} from '#/lib/strings/helpers'
|
import {countLines} from '#/lib/strings/helpers'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {
|
import {
|
||||||
POST_TOMBSTONE,
|
POST_TOMBSTONE,
|
||||||
type Shadow,
|
type Shadow,
|
||||||
@@ -68,7 +69,7 @@ export function Post({
|
|||||||
record
|
record
|
||||||
? new RichTextAPI({
|
? new RichTextAPI({
|
||||||
text: record.text,
|
text: record.text,
|
||||||
facets: record.facets,
|
facets: asSdkFacets(record.facets),
|
||||||
})
|
})
|
||||||
: undefined,
|
: undefined,
|
||||||
[record],
|
[record],
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import {
|
|||||||
AppBskyEmbedImages,
|
AppBskyEmbedImages,
|
||||||
AppBskyEmbedVideo,
|
AppBskyEmbedVideo,
|
||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
type RichText as RichTextType,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {type RichText as RichTextType} from '@bsky.app/sdk/richtext'
|
||||||
import {useLingui} from '@lingui/react/macro'
|
import {useLingui} from '@lingui/react/macro'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
AppBskyFeedThreadgate,
|
AppBskyFeedThreadgate,
|
||||||
AtUri,
|
AtUri,
|
||||||
type ModerationDecision,
|
type ModerationDecision,
|
||||||
RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {type ReasonFeedSource} from '#/lib/api/feed/types'
|
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 {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {countLines} from '#/lib/strings/helpers'
|
import {countLines} from '#/lib/strings/helpers'
|
||||||
|
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||||
import {
|
import {
|
||||||
POST_TOMBSTONE,
|
POST_TOMBSTONE,
|
||||||
type Shadow,
|
type Shadow,
|
||||||
@@ -104,7 +105,7 @@ export function PostFeedItem({
|
|||||||
() =>
|
() =>
|
||||||
new RichTextAPI({
|
new RichTextAPI({
|
||||||
text: record.text,
|
text: record.text,
|
||||||
facets: record.facets,
|
facets: asSdkFacets(record.facets),
|
||||||
}),
|
}),
|
||||||
[record],
|
[record],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import {
|
|||||||
type ModerationBehavior,
|
type ModerationBehavior,
|
||||||
type ModerationDecision,
|
type ModerationDecision,
|
||||||
type ModerationOpts,
|
type ModerationOpts,
|
||||||
RichText,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText} from '@bsky.app/sdk/richtext'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
type AppBskyActorDefs,
|
type AppBskyActorDefs,
|
||||||
moderateProfile,
|
moderateProfile,
|
||||||
type ModerationOpts,
|
type ModerationOpts,
|
||||||
RichText as RichTextAPI,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
@@ -33,7 +33,7 @@ import {useLabelerInfoQuery} from '#/state/queries/labeler'
|
|||||||
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
|
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
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 {ProfileFeedgens} from '#/view/com/feeds/ProfileFeedgens'
|
||||||
import {ProfileLists} from '#/view/com/lists/ProfileLists'
|
import {ProfileLists} from '#/view/com/lists/ProfileLists'
|
||||||
import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
|
import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
|
||||||
@@ -613,7 +613,11 @@ function ProfileScreenLoaded({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function useRichText(text: string): [RichTextAPI, boolean] {
|
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 [prevText, setPrevText] = useState(text)
|
||||||
const [rawRT, setRawRT] = useState(() => new RichTextAPI({text}))
|
const [rawRT, setRawRT] = useState(() => new RichTextAPI({text}))
|
||||||
const [resolvedRT, setResolvedRT] = useState<RichTextAPI | null>(null)
|
const [resolvedRT, setResolvedRT] = useState<RichTextAPI | null>(null)
|
||||||
@@ -628,7 +632,7 @@ function useRichText(text: string): [RichTextAPI, boolean] {
|
|||||||
async function resolveRTFacets() {
|
async function resolveRTFacets() {
|
||||||
// new each time
|
// new each time
|
||||||
const resolvedRT = new RichTextAPI({text})
|
const resolvedRT = new RichTextAPI({text})
|
||||||
await resolvedRT.detectFacets(agent)
|
await resolvedRT.detectFacets(client)
|
||||||
if (!ignore) {
|
if (!ignore) {
|
||||||
setResolvedRT(resolvedRT)
|
setResolvedRT(resolvedRT)
|
||||||
}
|
}
|
||||||
@@ -637,7 +641,7 @@ function useRichText(text: string): [RichTextAPI, boolean] {
|
|||||||
return () => {
|
return () => {
|
||||||
ignore = true
|
ignore = true
|
||||||
}
|
}
|
||||||
}, [text, agent])
|
}, [text, client])
|
||||||
const isResolving = resolvedRT === null
|
const isResolving = resolvedRT === null
|
||||||
return [resolvedRT ?? rawRT, isResolving]
|
return [resolvedRT ?? rawRT, isResolving]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user