diff options
-rw-r--r-- | src/lib/api/index.ts | 15 | ||||
-rw-r--r-- | src/lib/strings/rich-text-manip.ts | 24 | ||||
-rw-r--r-- | src/screens/Messages/Conversation/MessageInput.tsx | 2 | ||||
-rw-r--r-- | src/screens/Messages/Conversation/MessageInput.web.tsx | 2 | ||||
-rw-r--r-- | src/screens/Messages/Conversation/MessagesList.tsx | 85 | ||||
-rw-r--r-- | src/state/messages/convo/agent.ts | 2 | ||||
-rw-r--r-- | src/view/com/modals/CreateOrEditList.tsx | 20 |
7 files changed, 99 insertions, 51 deletions
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index bc50f9cb3..dfaae2e01 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -4,7 +4,6 @@ import { AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, AppBskyFeedThreadgate, - AppBskyRichtextFacet, BskyAgent, ComAtprotoLabelDefs, ComAtprotoRepoUploadBlob, @@ -15,7 +14,7 @@ import {AtUri} from '@atproto/api' import {logger} from '#/logger' import {ThreadgateSetting} from '#/state/queries/threadgate' import {isNetworkError} from 'lib/strings/errors' -import {shortenLinks} from 'lib/strings/rich-text-manip' +import {shortenLinks, stripInvalidMentions} from 'lib/strings/rich-text-manip' import {isNative, isWeb} from 'platform/detection' import {ImageModel} from 'state/models/media/image' import {LinkMeta} from '../link-meta/link-meta' @@ -81,17 +80,7 @@ export async function post(agent: BskyAgent, opts: PostOpts) { opts.onStateChange?.('Processing...') await rt.detectFacets(agent) rt = shortenLinks(rt) - - // filter out any mention facets that didn't map to a user - rt.facets = rt.facets?.filter(facet => { - const mention = facet.features.find(feature => - AppBskyRichtextFacet.isMention(feature), - ) - if (mention && !mention.did) { - return false - } - return true - }) + rt = stripInvalidMentions(rt) // add quote embed if present if (opts.quote) { diff --git a/src/lib/strings/rich-text-manip.ts b/src/lib/strings/rich-text-manip.ts index 508e0772e..2e84656f2 100644 --- a/src/lib/strings/rich-text-manip.ts +++ b/src/lib/strings/rich-text-manip.ts @@ -1,4 +1,4 @@ -import {RichText, UnicodeString} from '@atproto/api' +import {AppBskyRichtextFacet, RichText, UnicodeString} from '@atproto/api' import {toShortUrl} from './url-helpers' @@ -10,9 +10,7 @@ export function shortenLinks(rt: RichText): RichText { // enumerate the link facets if (rt.facets) { for (const facet of rt.facets) { - const isLink = !!facet.features.find( - f => f.$type === 'app.bsky.richtext.facet#link', - ) + const isLink = !!facet.features.find(AppBskyRichtextFacet.isLink) if (!isLink) { continue } @@ -33,3 +31,21 @@ export function shortenLinks(rt: RichText): RichText { } return rt } + +// filter out any mention facets that didn't map to a user +export function stripInvalidMentions(rt: RichText): RichText { + if (!rt.facets?.length) { + return rt + } + rt = rt.clone() + if (rt.facets) { + rt.facets = rt.facets?.filter(facet => { + const mention = facet.features.find(AppBskyRichtextFacet.isMention) + if (mention && !mention.did) { + return false + } + return true + }) + } + return rt +} diff --git a/src/screens/Messages/Conversation/MessageInput.tsx b/src/screens/Messages/Conversation/MessageInput.tsx index 698fc6b7a..149188684 100644 --- a/src/screens/Messages/Conversation/MessageInput.tsx +++ b/src/screens/Messages/Conversation/MessageInput.tsx @@ -63,7 +63,7 @@ export function MessageInput({ return } clearDraft() - onSendMessage(message.trimEnd()) + onSendMessage(message) playHaptic() setMessage('') diff --git a/src/screens/Messages/Conversation/MessageInput.web.tsx b/src/screens/Messages/Conversation/MessageInput.web.tsx index 5d8d568ff..a61355e55 100644 --- a/src/screens/Messages/Conversation/MessageInput.web.tsx +++ b/src/screens/Messages/Conversation/MessageInput.web.tsx @@ -43,7 +43,7 @@ export function MessageInput({ return } clearDraft() - onSendMessage(message.trimEnd()) + onSendMessage(message) setMessage('') }, [message, onSendMessage, _, clearDraft]) diff --git a/src/screens/Messages/Conversation/MessagesList.tsx b/src/screens/Messages/Conversation/MessagesList.tsx index 583c40852..d6aa06a1c 100644 --- a/src/screens/Messages/Conversation/MessagesList.tsx +++ b/src/screens/Messages/Conversation/MessagesList.tsx @@ -13,12 +13,16 @@ import { } from 'react-native-reanimated' import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/reanimated2/hook/commonTypes' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {AppBskyRichtextFacet, RichText} from '@atproto/api' +import {AppBskyEmbedRecord, AppBskyRichtextFacet, RichText} from '@atproto/api' -import {shortenLinks} from '#/lib/strings/rich-text-manip' +import {getPostAsQuote} from '#/lib/link-meta/bsky' +import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' +import {isBskyPostUrl} from '#/lib/strings/url-helpers' +import {logger} from '#/logger' import {isNative} from '#/platform/detection' import {isConvoActive, useConvoActive} from '#/state/messages/convo' import {ConvoItem, ConvoStatus} from '#/state/messages/convo/types' +import {useGetPost} from '#/state/queries/post' import {useAgent} from '#/state/session' import {clamp} from 'lib/numbers' import {ScrollProvider} from 'lib/ScrollContext' @@ -80,6 +84,7 @@ export function MessagesList({ }) { const convoState = useConvoActive() const agent = useAgent() + const getPost = useGetPost() const flatListRef = useAnimatedRef<FlatList>() @@ -264,20 +269,71 @@ export function MessagesList({ // -- Message sending const onSendMessage = useCallback( async (text: string) => { - let rt = new RichText({text}, {cleanNewlines: true}) - await rt.detectFacets(agent) - rt = shortenLinks(rt) + let rt = new RichText({text: text.trimEnd()}, {cleanNewlines: true}) + + // detect facets without resolution first - this is used to see if there's + // any post links in the text that we can embed. We do this first because + // we want to remove the post link from the text, re-trim, then detect facets + rt.detectFacetsWithoutResolution() + + let embed: AppBskyEmbedRecord.Main | undefined + // find the first link facet that is a link to a post + const postLinkFacet = rt.facets?.find(facet => { + return facet.features.find(feature => { + if (AppBskyRichtextFacet.isLink(feature)) { + return isBskyPostUrl(feature.uri) + } + return false + }) + }) - // filter out any mention facets that didn't map to a user - rt.facets = rt.facets?.filter(facet => { - const mention = facet.features.find(feature => - AppBskyRichtextFacet.isMention(feature), + // if we found a post link, get the post and embed it + if (postLinkFacet) { + const postLink = postLinkFacet.features.find( + AppBskyRichtextFacet.isLink, ) - if (mention && !mention.did) { - return false + if (!postLink) return + + try { + const post = await getPostAsQuote(getPost, postLink.uri) + if (post) { + embed = { + $type: 'app.bsky.embed.record', + record: { + uri: post.uri, + cid: post.cid, + }, + } + + // remove the post link from the text + rt.delete( + postLinkFacet.index.byteStart, + postLinkFacet.index.byteEnd, + ) + + // re-trim the text, now that we've removed the post link + // + // if the post link is at the start of the text, we don't want to leave a leading space + // so trim on both sides + if (postLinkFacet.index.byteStart === 0) { + rt = new RichText({text: rt.text.trim()}, {cleanNewlines: true}) + } else { + // otherwise just trim the end + rt = new RichText( + {text: rt.text.trimEnd()}, + {cleanNewlines: true}, + ) + } + } + } catch (error) { + logger.error('Failed to get post as quote for DM', {error}) } - return true - }) + } + + await rt.detectFacets(agent) + + rt = shortenLinks(rt) + rt = stripInvalidMentions(rt) if (!hasScrolled) { setHasScrolled(true) @@ -286,9 +342,10 @@ export function MessagesList({ convoState.sendMessage({ text: rt.text, facets: rt.facets, + embed, }) }, - [convoState, agent, hasScrolled, setHasScrolled], + [agent, convoState, getPost, hasScrolled, setHasScrolled], ) // -- List layout changes (opening emoji keyboard, etc.) diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index a0355ab07..9850124c9 100644 --- a/src/state/messages/convo/agent.ts +++ b/src/state/messages/convo/agent.ts @@ -753,7 +753,7 @@ export class Convo { sendMessage(message: ChatBskyConvoSendMessage.InputSchema['message']) { // Ignore empty messages for now since they have no other purpose atm - if (!message.text.trim()) return + if (!message.text.trim() && !message.embed) return logger.debug('Convo: send message', {}, logger.DebugContext.convo) diff --git a/src/view/com/modals/CreateOrEditList.tsx b/src/view/com/modals/CreateOrEditList.tsx index 2ea34e808..3088c92a1 100644 --- a/src/view/com/modals/CreateOrEditList.tsx +++ b/src/view/com/modals/CreateOrEditList.tsx @@ -10,16 +10,12 @@ import { } from 'react-native' import {Image as RNImage} from 'react-native-image-crop-picker' import {LinearGradient} from 'expo-linear-gradient' -import { - AppBskyGraphDefs, - AppBskyRichtextFacet, - RichText as RichTextAPI, -} from '@atproto/api' +import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {richTextToString} from '#/lib/strings/rich-text-helpers' -import {shortenLinks} from '#/lib/strings/rich-text-manip' +import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' import {useModalControls} from '#/state/modals' import { useListCreateMutation, @@ -159,17 +155,7 @@ export function Component({ await richText.detectFacets(agent) richText = shortenLinks(richText) - - // filter out any mention facets that didn't map to a user - richText.facets = richText.facets?.filter(facet => { - const mention = facet.features.find(feature => - AppBskyRichtextFacet.isMention(feature), - ) - if (mention && !mention.did) { - return false - } - return true - }) + richText = stripInvalidMentions(richText) if (list) { await listMetadataMutation.mutateAsync({ |