Fix DM embeds being dropped when sending (#10924)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,13 +37,14 @@ import {PaperPlaneVertical_Filled_Stroke2_Corner1_Rounded as PaperPlaneIcon} fro
|
|||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
|
import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
|
||||||
|
import {type MessageEmbedState} from './MessageInputEmbed'
|
||||||
|
|
||||||
const MIN_HEIGHT = 40
|
const MIN_HEIGHT = 40
|
||||||
|
|
||||||
export function MessageComposer({
|
export function MessageComposer({
|
||||||
textInputId,
|
textInputId,
|
||||||
onSendMessage,
|
onSendMessage,
|
||||||
hasEmbed,
|
messageEmbed,
|
||||||
setEmbed,
|
setEmbed,
|
||||||
children,
|
children,
|
||||||
loading = false,
|
loading = false,
|
||||||
@@ -51,9 +52,10 @@ export function MessageComposer({
|
|||||||
textInputId?: string
|
textInputId?: string
|
||||||
onSendMessage: (
|
onSendMessage: (
|
||||||
message: string,
|
message: string,
|
||||||
|
embed?: MessageEmbedState,
|
||||||
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
||||||
) => void
|
) => void
|
||||||
hasEmbed: boolean
|
messageEmbed: MessageEmbedState | undefined
|
||||||
setEmbed: (embedUrl: string | undefined) => void
|
setEmbed: (embedUrl: string | undefined) => void
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
@@ -89,14 +91,16 @@ export function MessageComposer({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const submitDisabled = !editable || (!hasEmbed && text.trim().length === 0)
|
const submitDisabled =
|
||||||
|
!editable || (!messageEmbed && text.trim().length === 0)
|
||||||
|
|
||||||
const onSubmit = (
|
const onSubmit = (
|
||||||
message: string,
|
message: string,
|
||||||
|
embed: MessageEmbedState | undefined,
|
||||||
replyTo: ChatBskyConvoDefs.MessageView | null,
|
replyTo: ChatBskyConvoDefs.MessageView | null,
|
||||||
) => {
|
) => {
|
||||||
if (!editable) return
|
if (!editable) return
|
||||||
if (!hasEmbed && message.trim() === '') return
|
if (!embed && message.trim() === '') return
|
||||||
const graphemeCount = countGraphemes(message)
|
const graphemeCount = countGraphemes(message)
|
||||||
if (graphemeCount > MAX_DM_GRAPHEME_LENGTH) {
|
if (graphemeCount > MAX_DM_GRAPHEME_LENGTH) {
|
||||||
Toast.show(
|
Toast.show(
|
||||||
@@ -120,6 +124,7 @@ export function MessageComposer({
|
|||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
onSendMessage(
|
onSendMessage(
|
||||||
message,
|
message,
|
||||||
|
embed,
|
||||||
replyTo
|
replyTo
|
||||||
? {
|
? {
|
||||||
...replyTo,
|
...replyTo,
|
||||||
@@ -152,18 +157,18 @@ export function MessageComposer({
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (isFlushingAutocorrectSuggestion.current) {
|
if (isFlushingAutocorrectSuggestion.current) {
|
||||||
isFlushingAutocorrectSuggestion.current = false
|
isFlushingAutocorrectSuggestion.current = false
|
||||||
onSubmit(text, replyTo)
|
onSubmit(text, messageEmbed, replyTo)
|
||||||
}
|
}
|
||||||
}, 20)
|
}, 20)
|
||||||
} else {
|
} else {
|
||||||
onSubmit(text, replyTo)
|
onSubmit(text, messageEmbed, replyTo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChange = (nextText: string) => {
|
const handleChange = (nextText: string) => {
|
||||||
if (IS_IOS && isFlushingAutocorrectSuggestion.current) {
|
if (IS_IOS && isFlushingAutocorrectSuggestion.current) {
|
||||||
isFlushingAutocorrectSuggestion.current = false
|
isFlushingAutocorrectSuggestion.current = false
|
||||||
onSubmit(nextText, replyTo)
|
onSubmit(nextText, messageEmbed, replyTo)
|
||||||
} else {
|
} else {
|
||||||
setText(nextText)
|
setText(nextText)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ import {Loader} from '#/components/Loader'
|
|||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env'
|
import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env'
|
||||||
import {ComposerContainer} from './MessageComposer'
|
import {ComposerContainer} from './MessageComposer'
|
||||||
import {useExtractEmbedFromFacets} from './MessageInputEmbed'
|
import {
|
||||||
|
type MessageEmbedState,
|
||||||
|
useExtractEmbedFromFacets,
|
||||||
|
} from './MessageInputEmbed'
|
||||||
|
|
||||||
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
|
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
|
||||||
|
|
||||||
@@ -43,7 +46,7 @@ const MIN_HEIGHT = 40
|
|||||||
export function MessageInput({
|
export function MessageInput({
|
||||||
textInputId,
|
textInputId,
|
||||||
onSendMessage,
|
onSendMessage,
|
||||||
hasEmbed,
|
messageEmbed,
|
||||||
setEmbed,
|
setEmbed,
|
||||||
children,
|
children,
|
||||||
loading = false,
|
loading = false,
|
||||||
@@ -51,9 +54,10 @@ export function MessageInput({
|
|||||||
textInputId?: string
|
textInputId?: string
|
||||||
onSendMessage: (
|
onSendMessage: (
|
||||||
message: string,
|
message: string,
|
||||||
|
embed?: MessageEmbedState,
|
||||||
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
||||||
) => Promise<void> | void
|
) => Promise<void> | void
|
||||||
hasEmbed: boolean
|
messageEmbed: MessageEmbedState | undefined
|
||||||
setEmbed: (embedUrl: string | undefined) => void
|
setEmbed: (embedUrl: string | undefined) => void
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
@@ -85,7 +89,7 @@ export function MessageInput({
|
|||||||
if (!editable) {
|
if (!editable) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!hasEmbed && message.trim() === '') {
|
if (!messageEmbed && message.trim() === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
|
if (countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
|
||||||
@@ -96,6 +100,8 @@ export function MessageInput({
|
|||||||
}
|
}
|
||||||
clearDraft()
|
clearDraft()
|
||||||
playHaptic()
|
playHaptic()
|
||||||
|
// Capture the embed before clearing - the deferred send below reads it.
|
||||||
|
const embed = messageEmbed
|
||||||
setEmbed(undefined)
|
setEmbed(undefined)
|
||||||
setMessage('')
|
setMessage('')
|
||||||
// Capture the reply before clearing - the deferred send below reads it.
|
// Capture the reply before clearing - the deferred send below reads it.
|
||||||
@@ -115,6 +121,7 @@ export function MessageInput({
|
|||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
void onSendMessage(
|
void onSendMessage(
|
||||||
message,
|
message,
|
||||||
|
embed,
|
||||||
reply
|
reply
|
||||||
? {...reply, $type: 'chat.bsky.convo.defs#messageView'}
|
? {...reply, $type: 'chat.bsky.convo.defs#messageView'}
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -122,7 +129,7 @@ export function MessageInput({
|
|||||||
})
|
})
|
||||||
}, [
|
}, [
|
||||||
editable,
|
editable,
|
||||||
hasEmbed,
|
messageEmbed,
|
||||||
message,
|
message,
|
||||||
clearDraft,
|
clearDraft,
|
||||||
onSendMessage,
|
onSendMessage,
|
||||||
@@ -159,7 +166,8 @@ export function MessageInput({
|
|||||||
scrollEnabled: isInputScrollable.get(),
|
scrollEnabled: isInputScrollable.get(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const submitDisabled = !editable || (!hasEmbed && message.trim().length === 0)
|
const submitDisabled =
|
||||||
|
!editable || (!messageEmbed && message.trim().length === 0)
|
||||||
|
|
||||||
const blur = useCallback(() => {
|
const blur = useCallback(() => {
|
||||||
inputRef.current?.blur()
|
inputRef.current?.blur()
|
||||||
|
|||||||
@@ -21,20 +21,24 @@ import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons
|
|||||||
import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
|
import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {IS_WEB_SAFARI, IS_WEB_TOUCH_DEVICE} from '#/env'
|
import {IS_WEB_SAFARI, IS_WEB_TOUCH_DEVICE} from '#/env'
|
||||||
import {useExtractEmbedFromFacets} from './MessageInputEmbed'
|
import {
|
||||||
|
type MessageEmbedState,
|
||||||
|
useExtractEmbedFromFacets,
|
||||||
|
} from './MessageInputEmbed'
|
||||||
|
|
||||||
export function MessageInput({
|
export function MessageInput({
|
||||||
onSendMessage,
|
onSendMessage,
|
||||||
hasEmbed,
|
messageEmbed,
|
||||||
setEmbed,
|
setEmbed,
|
||||||
children,
|
children,
|
||||||
loading = false,
|
loading = false,
|
||||||
}: {
|
}: {
|
||||||
onSendMessage: (
|
onSendMessage: (
|
||||||
message: string,
|
message: string,
|
||||||
|
embed?: MessageEmbedState,
|
||||||
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
||||||
) => void
|
) => void
|
||||||
hasEmbed: boolean
|
messageEmbed: MessageEmbedState | undefined
|
||||||
setEmbed: (embedUrl: string | undefined) => void
|
setEmbed: (embedUrl: string | undefined) => void
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
@@ -54,7 +58,7 @@ export function MessageInput({
|
|||||||
const textAreaRef = useRef<HTMLTextAreaElement>(null)
|
const textAreaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
|
|
||||||
const onSubmit = useCallback(() => {
|
const onSubmit = useCallback(() => {
|
||||||
if (!hasEmbed && message.trim() === '') {
|
if (!messageEmbed && message.trim() === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
|
if (countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
|
||||||
@@ -66,6 +70,7 @@ export function MessageInput({
|
|||||||
clearDraft()
|
clearDraft()
|
||||||
onSendMessage(
|
onSendMessage(
|
||||||
message,
|
message,
|
||||||
|
messageEmbed,
|
||||||
replyTo
|
replyTo
|
||||||
? {...replyTo, $type: 'chat.bsky.convo.defs#messageView'}
|
? {...replyTo, $type: 'chat.bsky.convo.defs#messageView'}
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -78,7 +83,7 @@ export function MessageInput({
|
|||||||
onSendMessage,
|
onSendMessage,
|
||||||
l,
|
l,
|
||||||
clearDraft,
|
clearDraft,
|
||||||
hasEmbed,
|
messageEmbed,
|
||||||
setEmbed,
|
setEmbed,
|
||||||
replyTo,
|
replyTo,
|
||||||
clearReply,
|
clearReply,
|
||||||
|
|||||||
@@ -378,7 +378,11 @@ export function MessagesList({
|
|||||||
|
|
||||||
// -- Message sending
|
// -- Message sending
|
||||||
const onSendMessage = useCallback(
|
const onSendMessage = useCallback(
|
||||||
async (text: string, reply?: $Typed<ChatBskyConvoDefs.MessageView>) => {
|
async (
|
||||||
|
text: string,
|
||||||
|
embedState?: MessageEmbedState,
|
||||||
|
reply?: $Typed<ChatBskyConvoDefs.MessageView>,
|
||||||
|
) => {
|
||||||
let rt = new RichText({text: text.trimEnd()}, {cleanNewlines: true})
|
let rt = new RichText({text: text.trimEnd()}, {cleanNewlines: true})
|
||||||
|
|
||||||
// detect facets without resolution first - this is used to see if there's
|
// detect facets without resolution first - this is used to see if there's
|
||||||
@@ -416,9 +420,9 @@ export function MessagesList({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageEmbed?.type === 'post') {
|
if (embedState?.type === 'post') {
|
||||||
try {
|
try {
|
||||||
const post = await getPost({uri: messageEmbed.uri})
|
const post = await getPost({uri: embedState.uri})
|
||||||
if (post) {
|
if (post) {
|
||||||
embed = {
|
embed = {
|
||||||
$type: 'app.bsky.embed.record',
|
$type: 'app.bsky.embed.record',
|
||||||
@@ -445,8 +449,8 @@ export function MessagesList({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to get post as quote for DM', {error})
|
logger.error('Failed to get post as quote for DM', {error})
|
||||||
}
|
}
|
||||||
} else if (messageEmbed?.type === 'invite') {
|
} else if (embedState?.type === 'invite') {
|
||||||
const code = messageEmbed.code
|
const code = embedState.code
|
||||||
embed = {
|
embed = {
|
||||||
$type: 'chat.bsky.embed.joinLink',
|
$type: 'chat.bsky.embed.joinLink',
|
||||||
code,
|
code,
|
||||||
@@ -512,7 +516,6 @@ export function MessagesList({
|
|||||||
[
|
[
|
||||||
agent,
|
agent,
|
||||||
convoState,
|
convoState,
|
||||||
messageEmbed,
|
|
||||||
getPost,
|
getPost,
|
||||||
getJoinLinkPreview,
|
getJoinLinkPreview,
|
||||||
hasSession,
|
hasSession,
|
||||||
@@ -737,6 +740,7 @@ function Composer({
|
|||||||
textInputId: string
|
textInputId: string
|
||||||
onSendMessage: (
|
onSendMessage: (
|
||||||
message: string,
|
message: string,
|
||||||
|
embed?: MessageEmbedState,
|
||||||
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
messageEmbed: MessageEmbedState | undefined
|
messageEmbed: MessageEmbedState | undefined
|
||||||
@@ -745,8 +749,12 @@ function Composer({
|
|||||||
useNewComposer: boolean
|
useNewComposer: boolean
|
||||||
}) {
|
}) {
|
||||||
const handleSendMessage = useNonReactiveCallback(
|
const handleSendMessage = useNonReactiveCallback(
|
||||||
(message: string, replyTo?: $Typed<ChatBskyConvoDefs.MessageView>) => {
|
(
|
||||||
void onSendMessage(message, replyTo)
|
message: string,
|
||||||
|
embed?: MessageEmbedState,
|
||||||
|
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
||||||
|
) => {
|
||||||
|
void onSendMessage(message, embed, replyTo)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -761,7 +769,7 @@ function Composer({
|
|||||||
<MessageComposer
|
<MessageComposer
|
||||||
textInputId={textInputId}
|
textInputId={textInputId}
|
||||||
onSendMessage={handleSendMessage}
|
onSendMessage={handleSendMessage}
|
||||||
hasEmbed={!!messageEmbed}
|
messageEmbed={messageEmbed}
|
||||||
setEmbed={setEmbed}
|
setEmbed={setEmbed}
|
||||||
loading={loading}>
|
loading={loading}>
|
||||||
{previews}
|
{previews}
|
||||||
@@ -770,7 +778,7 @@ function Composer({
|
|||||||
<MessageInput
|
<MessageInput
|
||||||
textInputId={textInputId}
|
textInputId={textInputId}
|
||||||
onSendMessage={handleSendMessage}
|
onSendMessage={handleSendMessage}
|
||||||
hasEmbed={!!messageEmbed}
|
messageEmbed={messageEmbed}
|
||||||
setEmbed={setEmbed}
|
setEmbed={setEmbed}
|
||||||
loading={loading}>
|
loading={loading}>
|
||||||
{previews}
|
{previews}
|
||||||
|
|||||||
Reference in New Issue
Block a user