Remove old message composer (#10951)
(cherry picked from commit cd4d62c75a)
This commit is contained in:
@@ -10,7 +10,6 @@ export enum Features {
|
||||
ImportContactsSettingsDisable = 'import_contacts:settings:disable',
|
||||
LiveNowBetaDisable = 'live_now_beta:disable',
|
||||
GroupChatsDisable = 'group_chats:disable',
|
||||
DmsNewMessageComposerEnable = 'dms:new_message_composer:enable',
|
||||
ComposerLanguageDetectionEnable = 'composer:language_detection:enable',
|
||||
PostGalleryEmbedEnable = 'post_gallery_embed:enable',
|
||||
NotificationsExpandedProfileCardEnable = 'notifications:expanded_profile_card:enable',
|
||||
|
||||
@@ -334,8 +334,7 @@ function SubmitButton({
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: remove export when MessageInput is deleted
|
||||
export function ComposerContainer({children}: {children: React.ReactNode}) {
|
||||
function ComposerContainer({children}: {children: React.ReactNode}) {
|
||||
const {bottom: bottomInset} = useSafeAreaInsets()
|
||||
const {progress} = useReanimatedKeyboardAnimation()
|
||||
const t = useTheme()
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
import {useCallback, useState} from 'react'
|
||||
import {Pressable, TextInput, useWindowDimensions} from 'react-native'
|
||||
import {
|
||||
useFocusedInputHandler,
|
||||
useKeyboardHandler,
|
||||
useReanimatedKeyboardAnimation,
|
||||
} from 'react-native-keyboard-controller'
|
||||
import Animated, {
|
||||
measure,
|
||||
runOnJS,
|
||||
useAnimatedProps,
|
||||
useAnimatedRef,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
} from 'react-native-reanimated'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {GlassContainer} from 'expo-glass-effect'
|
||||
import {type $Typed, type ChatBskyConvoDefs} from '@atproto/api'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
import {countGraphemes} from 'unicode-segmenter/grapheme'
|
||||
|
||||
import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {useEmail} from '#/state/email-verification'
|
||||
import {
|
||||
useMessageDraft,
|
||||
useSaveMessageDraft,
|
||||
} from '#/state/messages/message-drafts'
|
||||
import {atoms as a, platform, tokens, useTheme} from '#/alf'
|
||||
import {useMessageReplies} from '#/components/dms/MessageReplies'
|
||||
import {GlassView} from '#/components/GlassView'
|
||||
import {PaperPlaneVertical_Filled_Stroke2_Corner1_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env'
|
||||
import {ComposerContainer} from './MessageComposer'
|
||||
import {
|
||||
type MessageEmbedState,
|
||||
useExtractEmbedFromFacets,
|
||||
} from './MessageInputEmbed'
|
||||
|
||||
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
|
||||
|
||||
const MIN_HEIGHT = 40
|
||||
|
||||
export function MessageInput({
|
||||
textInputId,
|
||||
onSendMessage,
|
||||
messageEmbed,
|
||||
setEmbed,
|
||||
children,
|
||||
loading = false,
|
||||
}: {
|
||||
textInputId?: string
|
||||
onSendMessage: (
|
||||
message: string,
|
||||
embed?: MessageEmbedState,
|
||||
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
||||
) => Promise<void> | void
|
||||
messageEmbed: MessageEmbedState | undefined
|
||||
setEmbed: (embedUrl: string | undefined) => void
|
||||
children?: React.ReactNode
|
||||
loading?: boolean
|
||||
}) {
|
||||
const {t: l} = useLingui()
|
||||
const t = useTheme()
|
||||
const playHaptic = useHaptics()
|
||||
const {getDraft, clearDraft} = useMessageDraft()
|
||||
const {replyTo, clearReply} = useMessageReplies()
|
||||
|
||||
// Input layout
|
||||
const {top: topInset} = useSafeAreaInsets()
|
||||
const {height: windowHeight} = useWindowDimensions()
|
||||
const {height: keyboardHeight} = useReanimatedKeyboardAnimation()
|
||||
const maxHeight = useSharedValue<undefined | number>(undefined)
|
||||
const isInputScrollable = useSharedValue(false)
|
||||
|
||||
const [message, setMessage] = useState(getDraft)
|
||||
const inputRef = useAnimatedRef<TextInput>()
|
||||
const [shouldEnforceClear, setShouldEnforceClear] = useState(false)
|
||||
|
||||
const {needsEmailVerification} = useEmail()
|
||||
const editable = !needsEmailVerification && !loading
|
||||
|
||||
useSaveMessageDraft(message)
|
||||
useExtractEmbedFromFacets(message, setEmbed)
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
if (!editable) {
|
||||
return
|
||||
}
|
||||
if (!messageEmbed && message.trim() === '') {
|
||||
return
|
||||
}
|
||||
if (countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
|
||||
Toast.show(l`Message is too long`, {
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
clearDraft()
|
||||
playHaptic()
|
||||
// Capture the embed before clearing - the deferred send below reads it.
|
||||
const embed = messageEmbed
|
||||
setEmbed(undefined)
|
||||
setMessage('')
|
||||
// Capture the reply before clearing - the deferred send below reads it.
|
||||
const reply = replyTo
|
||||
clearReply()
|
||||
if (IS_IOS) {
|
||||
setShouldEnforceClear(true)
|
||||
}
|
||||
if (IS_WEB) {
|
||||
// Pressing the send button causes the text input to lose focus, so we need to
|
||||
// re-focus it after sending
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
void onSendMessage(
|
||||
message,
|
||||
embed,
|
||||
reply
|
||||
? {...reply, $type: 'chat.bsky.convo.defs#messageView'}
|
||||
: undefined,
|
||||
)
|
||||
})
|
||||
}, [
|
||||
editable,
|
||||
messageEmbed,
|
||||
message,
|
||||
clearDraft,
|
||||
onSendMessage,
|
||||
playHaptic,
|
||||
setEmbed,
|
||||
inputRef,
|
||||
l,
|
||||
replyTo,
|
||||
clearReply,
|
||||
])
|
||||
|
||||
useFocusedInputHandler(
|
||||
{
|
||||
onChangeText: () => {
|
||||
'worklet'
|
||||
const measurement = measure(inputRef)
|
||||
if (!measurement) return
|
||||
|
||||
const max = windowHeight - -keyboardHeight.get() - topInset - 150
|
||||
const availableSpace = max - measurement.height
|
||||
|
||||
maxHeight.set(max)
|
||||
isInputScrollable.set(availableSpace < 30)
|
||||
},
|
||||
},
|
||||
[windowHeight, topInset],
|
||||
)
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
maxHeight: maxHeight.get(),
|
||||
}))
|
||||
|
||||
const animatedProps = useAnimatedProps(() => ({
|
||||
scrollEnabled: isInputScrollable.get(),
|
||||
}))
|
||||
|
||||
const submitDisabled =
|
||||
!editable || (!messageEmbed && message.trim().length === 0)
|
||||
|
||||
const blur = useCallback(() => {
|
||||
inputRef.current?.blur()
|
||||
}, [inputRef])
|
||||
|
||||
useKeyboardHandler({
|
||||
onEnd: evt => {
|
||||
'worklet'
|
||||
// small hack: interactive dismiss on Android sometimes doesn't blur the input
|
||||
if (IS_ANDROID && evt.progress === 0) {
|
||||
runOnJS(blur)()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<ComposerContainer>
|
||||
<GlassContainer
|
||||
style={[a.flex_row, a.align_end, a.gap_sm]}
|
||||
spacing={tokens.space.xs}>
|
||||
<GlassView
|
||||
isInteractive
|
||||
glassEffectStyle="regular"
|
||||
style={[a.flex_1, a.rounded_xl, {minHeight: MIN_HEIGHT}]}
|
||||
tintColor={t.palette.contrast_50}
|
||||
fallbackStyle={[t.atoms.bg_contrast_50]}>
|
||||
{children}
|
||||
<AnimatedTextInput
|
||||
nativeID={textInputId}
|
||||
accessibilityLabel={l`Message input field`}
|
||||
accessibilityHint={l`Type your message here`}
|
||||
placeholder={l`Message`}
|
||||
placeholderTextColor={t.palette.contrast_500}
|
||||
value={message}
|
||||
onChange={evt => {
|
||||
// bit of a hack: iOS automatically accepts autocomplete suggestions when you tap anywhere on the screen
|
||||
// including the button we just pressed - and this overrides clearing the input! so we watch for the
|
||||
// next change and double make sure the input is cleared. It should *always* send an onChange event after
|
||||
// clearing via setMessage('') that happens in onSubmit()
|
||||
// -sfn
|
||||
if (IS_IOS && shouldEnforceClear) {
|
||||
setShouldEnforceClear(false)
|
||||
setMessage('')
|
||||
return
|
||||
}
|
||||
const text = evt.nativeEvent.text
|
||||
setMessage(text)
|
||||
}}
|
||||
multiline={true}
|
||||
style={[
|
||||
{flexBasis: 'auto', minHeight: MIN_HEIGHT},
|
||||
a.flex_shrink_0,
|
||||
a.flex_grow,
|
||||
a.text_md,
|
||||
a.px_lg,
|
||||
t.atoms.text,
|
||||
platform({
|
||||
android: {paddingTop: 2, paddingBottom: 3},
|
||||
ios: {paddingTop: 10, paddingBottom: 5},
|
||||
}),
|
||||
animatedStyle,
|
||||
]}
|
||||
verticalAlign="middle"
|
||||
keyboardAppearance={t.scheme}
|
||||
submitBehavior="newline"
|
||||
ref={inputRef}
|
||||
hitSlop={HITSLOP_10}
|
||||
animatedProps={animatedProps}
|
||||
editable={editable}
|
||||
/>
|
||||
</GlassView>
|
||||
<GlassView
|
||||
isInteractive
|
||||
glassEffectStyle="regular"
|
||||
style={[a.rounded_full]}
|
||||
tintColor={
|
||||
submitDisabled ? t.palette.contrast_100 : t.palette.primary_500
|
||||
}
|
||||
fallbackStyle={{
|
||||
backgroundColor: submitDisabled
|
||||
? t.palette.contrast_100
|
||||
: t.palette.primary_500,
|
||||
}}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={
|
||||
loading
|
||||
? l({message: 'Loading chat…', context: 'placeholder'})
|
||||
: l({message: 'Message', context: 'action'})
|
||||
}
|
||||
accessibilityHint=""
|
||||
hitSlop={HITSLOP_10}
|
||||
style={[
|
||||
a.rounded_full,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
{
|
||||
height: MIN_HEIGHT,
|
||||
width: MIN_HEIGHT,
|
||||
},
|
||||
]}
|
||||
onPress={onSubmit}
|
||||
disabled={submitDisabled}>
|
||||
{loading ? (
|
||||
<Loader size="md" fill={t.palette.white} style={[a.mb_2xs]} />
|
||||
) : (
|
||||
<PaperPlaneIcon
|
||||
size="md"
|
||||
fill={t.palette.white}
|
||||
style={[a.mb_2xs]}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
</GlassView>
|
||||
</GlassContainer>
|
||||
</ComposerContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
import {useCallback, useRef, useState} from 'react'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import {type $Typed, type ChatBskyConvoDefs} from '@atproto/api'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
import {flushSync} from 'react-dom'
|
||||
import TextareaAutosize from 'react-textarea-autosize'
|
||||
import {countGraphemes} from 'unicode-segmenter/grapheme'
|
||||
|
||||
import {MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {
|
||||
useMessageDraft,
|
||||
useSaveMessageDraft,
|
||||
} from '#/state/messages/message-drafts'
|
||||
import {atoms as a, flatten, useTheme} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import {useMessageReplies} from '#/components/dms/MessageReplies'
|
||||
import * as EmojiPicker from '#/components/EmojiPicker'
|
||||
import {useSharedInputStyles} from '#/components/forms/TextField'
|
||||
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
|
||||
import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {IS_WEB_SAFARI, IS_WEB_TOUCH_DEVICE} from '#/env'
|
||||
import {
|
||||
type MessageEmbedState,
|
||||
useExtractEmbedFromFacets,
|
||||
} from './MessageInputEmbed'
|
||||
|
||||
export function MessageInput({
|
||||
onSendMessage,
|
||||
messageEmbed,
|
||||
setEmbed,
|
||||
children,
|
||||
loading = false,
|
||||
}: {
|
||||
onSendMessage: (
|
||||
message: string,
|
||||
embed?: MessageEmbedState,
|
||||
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
|
||||
) => void
|
||||
messageEmbed: MessageEmbedState | undefined
|
||||
setEmbed: (embedUrl: string | undefined) => void
|
||||
children?: React.ReactNode
|
||||
loading?: boolean
|
||||
}) {
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const {t: l} = useLingui()
|
||||
const t = useTheme()
|
||||
const {getDraft, clearDraft} = useMessageDraft()
|
||||
const {replyTo, clearReply} = useMessageReplies()
|
||||
const [message, setMessage] = useState(getDraft)
|
||||
|
||||
const inputStyles = useSharedInputStyles()
|
||||
const isComposing = useRef(false)
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const [textAreaHeight, setTextAreaHeight] = useState(38)
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
if (!messageEmbed && message.trim() === '') {
|
||||
return
|
||||
}
|
||||
if (countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
|
||||
Toast.show(l`Message is too long`, {
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
clearDraft()
|
||||
onSendMessage(
|
||||
message,
|
||||
messageEmbed,
|
||||
replyTo
|
||||
? {...replyTo, $type: 'chat.bsky.convo.defs#messageView'}
|
||||
: undefined,
|
||||
)
|
||||
clearReply()
|
||||
setMessage('')
|
||||
setEmbed(undefined)
|
||||
}, [
|
||||
message,
|
||||
onSendMessage,
|
||||
l,
|
||||
clearDraft,
|
||||
messageEmbed,
|
||||
setEmbed,
|
||||
replyTo,
|
||||
clearReply,
|
||||
])
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
// Don't submit the form when the Japanese or any other IME is composing
|
||||
if (isComposing.current) return
|
||||
|
||||
// see https://github.com/bluesky-social/social-app/issues/4178
|
||||
// see https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/
|
||||
// see https://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html
|
||||
//
|
||||
// On Safari, the final keydown event to dismiss the IME - which is the enter key - is also "Enter" below.
|
||||
// Obviously, this causes problems because the final dismissal should _not_ submit the text, but should just
|
||||
// stop the IME editing. This is the behavior of Chrome and Firefox, but not Safari.
|
||||
//
|
||||
// Keycode is deprecated, however the alternative seems to only be to compare the timestamp from the
|
||||
// onCompositionEnd event to the timestamp of the keydown event, which is not reliable. For example, this hack
|
||||
// uses that method: https://github.com/ProseMirror/prosemirror-view/pull/44. However, from my 500ms resulted in
|
||||
// far too long of a delay, and a subsequent enter press would often just end up doing nothing. A shorter time
|
||||
// frame was also not great, since it was too short to be reliable (i.e. an older system might have a larger
|
||||
// time gap between the two events firing.
|
||||
if (IS_WEB_SAFARI && e.key === 'Enter' && e.keyCode === 229) {
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === 'Enter') {
|
||||
if (e.shiftKey) return
|
||||
e.preventDefault()
|
||||
onSubmit()
|
||||
}
|
||||
},
|
||||
[onSubmit],
|
||||
)
|
||||
|
||||
const onChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setMessage(e.target.value)
|
||||
}, [])
|
||||
|
||||
const onEmojiInserted = useCallback(
|
||||
(emoji: EmojiPicker.Emoji) => {
|
||||
if (!textAreaRef.current) {
|
||||
return
|
||||
}
|
||||
const position = textAreaRef.current.selectionStart ?? 0
|
||||
flushSync(() => {
|
||||
setMessage(
|
||||
message =>
|
||||
message.slice(0, position) + emoji.native + message.slice(position),
|
||||
)
|
||||
})
|
||||
textAreaRef.current.selectionStart = position + emoji.native.length
|
||||
textAreaRef.current.selectionEnd = position + emoji.native.length
|
||||
},
|
||||
[setMessage],
|
||||
)
|
||||
|
||||
useSaveMessageDraft(message)
|
||||
useExtractEmbedFromFacets(message, setEmbed)
|
||||
|
||||
return (
|
||||
<View style={a.p_sm}>
|
||||
{children}
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
t.atoms.bg_contrast_25,
|
||||
{
|
||||
paddingRight: a.p_sm.padding - 2,
|
||||
paddingLeft: a.p_sm.padding - 2,
|
||||
borderWidth: 1,
|
||||
borderRadius: 23,
|
||||
borderColor: 'transparent',
|
||||
height: textAreaHeight + 23,
|
||||
},
|
||||
isHovered && inputStyles.chromeHover,
|
||||
isFocused && inputStyles.chromeFocus,
|
||||
]}
|
||||
// @ts-expect-error web only
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}>
|
||||
{loading ? null : (
|
||||
<EmojiPicker.Root
|
||||
onEmojiSelect={onEmojiInserted}
|
||||
nextFocusRef={textAreaRef}>
|
||||
<EmojiPicker.Trigger label={l`Open emoji picker`}>
|
||||
{({props, state}) => (
|
||||
<Button
|
||||
style={[
|
||||
a.rounded_full,
|
||||
a.overflow_hidden,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
{
|
||||
marginTop: 5,
|
||||
height: 30,
|
||||
width: 30,
|
||||
},
|
||||
]}
|
||||
label={props.accessibilityLabel}
|
||||
{...props}>
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
{
|
||||
backgroundColor:
|
||||
state.hovered || state.focused || state.pressed
|
||||
? t.atoms.bg.backgroundColor
|
||||
: undefined,
|
||||
},
|
||||
]}>
|
||||
<EmojiSmile size="lg" />
|
||||
</View>
|
||||
</Button>
|
||||
)}
|
||||
</EmojiPicker.Trigger>
|
||||
<EmojiPicker.Picker />
|
||||
</EmojiPicker.Root>
|
||||
)}
|
||||
<TextareaAutosize
|
||||
ref={textAreaRef}
|
||||
disabled={loading}
|
||||
style={flatten([
|
||||
a.flex_1,
|
||||
a.px_sm,
|
||||
a.border_0,
|
||||
t.atoms.text,
|
||||
{
|
||||
paddingTop: 10,
|
||||
backgroundColor: 'transparent',
|
||||
resize: 'none',
|
||||
},
|
||||
])}
|
||||
maxRows={12}
|
||||
placeholder={
|
||||
loading
|
||||
? l({message: 'Loading chat…', context: 'placeholder'})
|
||||
: l({message: 'Message', context: 'action'})
|
||||
}
|
||||
defaultValue=""
|
||||
value={message}
|
||||
dirName="ltr"
|
||||
autoFocus={true}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
onCompositionStart={() => {
|
||||
isComposing.current = true
|
||||
}}
|
||||
onCompositionEnd={() => {
|
||||
isComposing.current = false
|
||||
}}
|
||||
onHeightChange={height => setTextAreaHeight(height)}
|
||||
onChange={onChange}
|
||||
// On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message
|
||||
// in these cases.
|
||||
onKeyDown={IS_WEB_TOUCH_DEVICE && isMobile ? undefined : onKeyDown}
|
||||
/>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={l`Send message`}
|
||||
accessibilityHint=""
|
||||
disabled={loading}
|
||||
style={[
|
||||
a.rounded_full,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
{
|
||||
height: 30,
|
||||
width: 30,
|
||||
marginTop: 5,
|
||||
backgroundColor: t.palette.primary_500,
|
||||
},
|
||||
]}
|
||||
onPress={onSubmit}>
|
||||
<PaperPlane fill={t.palette.white} style={[a.relative, {left: 1}]} />
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -57,7 +57,6 @@ import {createEmbedViewRecordFromPost} from '#/state/queries/postgate/util'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {List, type ListMethods} from '#/view/com/util/List'
|
||||
import {MessageComposer} from '#/screens/Messages/components/MessageComposer'
|
||||
import {MessageInput} from '#/screens/Messages/components/MessageInput'
|
||||
import {MessageListError} from '#/screens/Messages/components/MessageListError'
|
||||
import {atoms as a, platform, tokens, useTheme, web} from '#/alf'
|
||||
import {DateDivider} from '#/components/dms/DateDivider'
|
||||
@@ -704,9 +703,6 @@ export function MessagesList({
|
||||
messageEmbed={messageEmbed}
|
||||
setEmbed={setEmbed}
|
||||
loading={loading}
|
||||
useNewComposer={ax.features.enabled(
|
||||
ax.features.DmsNewMessageComposerEnable,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</ConversationFooter>
|
||||
@@ -735,7 +731,6 @@ function Composer({
|
||||
messageEmbed,
|
||||
setEmbed,
|
||||
loading,
|
||||
useNewComposer,
|
||||
}: {
|
||||
textInputId: string
|
||||
onSendMessage: (
|
||||
@@ -746,7 +741,6 @@ function Composer({
|
||||
messageEmbed: MessageEmbedState | undefined
|
||||
setEmbed: (embedUrl: string | undefined) => void
|
||||
loading?: boolean
|
||||
useNewComposer: boolean
|
||||
}) {
|
||||
const handleSendMessage = useNonReactiveCallback(
|
||||
(
|
||||
@@ -758,31 +752,16 @@ function Composer({
|
||||
},
|
||||
)
|
||||
|
||||
const previews = (
|
||||
<>
|
||||
<MessageInputReply />
|
||||
<MessageInputEmbed embed={messageEmbed} setEmbed={setEmbed} />
|
||||
</>
|
||||
)
|
||||
|
||||
return useNewComposer ? (
|
||||
return (
|
||||
<MessageComposer
|
||||
textInputId={textInputId}
|
||||
onSendMessage={handleSendMessage}
|
||||
messageEmbed={messageEmbed}
|
||||
setEmbed={setEmbed}
|
||||
loading={loading}>
|
||||
{previews}
|
||||
<MessageInputReply />
|
||||
<MessageInputEmbed embed={messageEmbed} setEmbed={setEmbed} />
|
||||
</MessageComposer>
|
||||
) : (
|
||||
<MessageInput
|
||||
textInputId={textInputId}
|
||||
onSendMessage={handleSendMessage}
|
||||
messageEmbed={messageEmbed}
|
||||
setEmbed={setEmbed}
|
||||
loading={loading}>
|
||||
{previews}
|
||||
</MessageInput>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user