Display more information in chat replies (#10950)
This commit is contained in:
@@ -44,12 +44,13 @@ import {useProfileBlockMutationQueue} from '#/state/queries/profile'
|
||||
import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache'
|
||||
import {useSession} from '#/state/session'
|
||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {atoms as a, native, platform, useTheme, utils} from '#/alf'
|
||||
import {atoms as a, native, platform, tokens, useTheme, utils} from '#/alf'
|
||||
import {isOnlyEmoji} from '#/alf/typography'
|
||||
import {Button} from '#/components/Button'
|
||||
import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
|
||||
import {useMessageDialogs} from '#/components/dms/MessageOverlays'
|
||||
import {useMessageReplies} from '#/components/dms/MessageReplies'
|
||||
import {useReplyPreviewText} from '#/components/dms/replyPreview'
|
||||
import {ArrowCornerDownRight_Stroke2_Corner3_Rounded as ArrowCornerDownRightIcon} from '#/components/icons/ArrowCornerDownRight'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
@@ -834,6 +835,7 @@ function ReplyQuote({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
const getReplyPreviewText = useReplyPreviewText()
|
||||
|
||||
const senderProfile = useMaybeProfileShadow(
|
||||
relatedProfiles.get(replyTo.sender.did),
|
||||
@@ -857,22 +859,15 @@ function ReplyQuote({
|
||||
let text: string
|
||||
let subtle = false
|
||||
if (isBlocked) {
|
||||
text = l`Blocked message hidden`
|
||||
text = l({
|
||||
message: '(blocked message hidden)',
|
||||
comment: 'A reply summary in chat',
|
||||
})
|
||||
subtle = true
|
||||
} else if (ChatBskyConvoDefs.isMessageView(replyTo)) {
|
||||
text = replyTo.text
|
||||
if (!text.trim()) {
|
||||
subtle = true
|
||||
if (ChatBskyEmbedJoinLink.isView(replyTo.embed)) {
|
||||
text = l`(chat invite link)`
|
||||
} else if (AppBskyEmbedRecord.isView(replyTo.embed)) {
|
||||
text = l`(contains embedded content)`
|
||||
} else {
|
||||
text = l`No text`
|
||||
}
|
||||
}
|
||||
;({text, subtle} = getReplyPreviewText(replyTo))
|
||||
} else {
|
||||
text = l`Deleted message`
|
||||
text = l({message: '(deleted message)', comment: 'A reply summary in chat'})
|
||||
subtle = true
|
||||
}
|
||||
|
||||
@@ -888,6 +883,8 @@ function ReplyQuote({
|
||||
a.mb_xs,
|
||||
a.rounded_md,
|
||||
a.p_sm,
|
||||
// The padding above is a little loose, so we tighten it up here.
|
||||
{paddingTop: tokens.space.sm - 2},
|
||||
a.flex_col,
|
||||
a.align_start,
|
||||
a.border,
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
AppBskyEmbedExternal,
|
||||
AppBskyEmbedRecord,
|
||||
type ChatBskyConvoDefs,
|
||||
ChatBskyEmbedJoinLink,
|
||||
ChatBskyGroupDefs,
|
||||
} from '@atproto/api'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {BSKY_APP_HOST, toShortUrl} from '#/lib/strings/url-helpers'
|
||||
|
||||
/**
|
||||
* Describes the embed of a quoted message that has no text of its own, so the
|
||||
* reply preview can show what was shared instead of a generic placeholder. For
|
||||
* a link card we surface the URI directly; otherwise we classify the quoted
|
||||
* record (post/feed/list/etc.) so the caller can render a translated label.
|
||||
*/
|
||||
type ReplyEmbedSummary =
|
||||
| {type: 'external'; uri: string}
|
||||
| {type: 'post'}
|
||||
| {type: 'unknown'}
|
||||
|
||||
function summarizeReplyEmbed(
|
||||
embed: ChatBskyConvoDefs.MessageView['embed'],
|
||||
): ReplyEmbedSummary {
|
||||
if (!AppBskyEmbedRecord.isView(embed)) return {type: 'unknown'}
|
||||
const {record} = embed
|
||||
if (AppBskyEmbedRecord.isViewRecord(record)) {
|
||||
const inner = record.embeds?.[0]
|
||||
if (AppBskyEmbedExternal.isView(inner)) {
|
||||
return {type: 'external', uri: inner.external.uri}
|
||||
}
|
||||
return {type: 'post'}
|
||||
}
|
||||
return {type: 'unknown'}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatter that computes the preview text for a message being quoted
|
||||
* in a reply, shared between the staged-reply composer and the sent reply
|
||||
* bubble so the two stay in sync. When the message has its own text we use it
|
||||
* verbatim; otherwise we summarize the embed.
|
||||
*
|
||||
* `subtle` indicates the text is a placeholder (not real message content), so
|
||||
* callers can render it in a muted/italic style.
|
||||
*/
|
||||
export function useReplyPreviewText(): (
|
||||
message: ChatBskyConvoDefs.MessageView,
|
||||
) => {text: string; subtle: boolean} {
|
||||
const {t: l} = useLingui()
|
||||
|
||||
return (message: ChatBskyConvoDefs.MessageView) => {
|
||||
const text = message.text
|
||||
if (text.trim()) {
|
||||
return {text, subtle: false}
|
||||
}
|
||||
|
||||
if (ChatBskyEmbedJoinLink.isView(message.embed)) {
|
||||
const {joinLinkPreview} = message.embed
|
||||
if (ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview)) {
|
||||
return {
|
||||
text: `${BSKY_APP_HOST}/chat/${joinLinkPreview.code}`,
|
||||
subtle: true,
|
||||
}
|
||||
}
|
||||
if (ChatBskyGroupDefs.isDisabledJoinLinkPreviewView(joinLinkPreview)) {
|
||||
return {
|
||||
text: l({
|
||||
message: '(disabled chat invite link)',
|
||||
comment: 'A reply summary in chat',
|
||||
}),
|
||||
subtle: true,
|
||||
}
|
||||
}
|
||||
if (ChatBskyGroupDefs.isInvalidJoinLinkPreviewView(joinLinkPreview)) {
|
||||
return {
|
||||
text: l({
|
||||
message: '(invalid chat invite link)',
|
||||
comment: 'A reply summary in chat',
|
||||
}),
|
||||
subtle: true,
|
||||
}
|
||||
}
|
||||
return {
|
||||
text: l({
|
||||
message: '(chat invite link)',
|
||||
comment: 'A reply summary in chat',
|
||||
}),
|
||||
subtle: true,
|
||||
}
|
||||
}
|
||||
|
||||
const summary = summarizeReplyEmbed(message.embed)
|
||||
switch (summary.type) {
|
||||
case 'external':
|
||||
return {text: toShortUrl(summary.uri), subtle: true}
|
||||
case 'post':
|
||||
return {
|
||||
text: l({
|
||||
message: '(quoted post)',
|
||||
comment: 'A reply summary in chat',
|
||||
}),
|
||||
subtle: true,
|
||||
}
|
||||
default:
|
||||
return {
|
||||
text: l({message: '(no text)', comment: 'A reply summary in chat'}),
|
||||
subtle: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import {LayoutAnimation, View} from 'react-native'
|
||||
import {AppBskyEmbedRecord, ChatBskyEmbedJoinLink} from '@atproto/api'
|
||||
import {type ChatBskyConvoDefs} from '@atproto/api'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {HITSLOP_20} from '#/lib/constants'
|
||||
@@ -8,6 +8,7 @@ import {useConvoActive} from '#/state/messages/convo'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import {useMessageReplies} from '#/components/dms/MessageReplies'
|
||||
import {useReplyPreviewText} from '#/components/dms/replyPreview'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
@@ -16,15 +17,27 @@ import {Text} from '#/components/Typography'
|
||||
* being replied to, with a button to cancel the reply.
|
||||
*/
|
||||
export function MessageInputReply() {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
const convo = useConvoActive()
|
||||
const {replyTo, clearReply} = useMessageReplies()
|
||||
const {replyTo} = useMessageReplies()
|
||||
|
||||
if (!replyTo) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <MessageInputReplyInner replyTo={replyTo} />
|
||||
}
|
||||
|
||||
function MessageInputReplyInner({
|
||||
replyTo,
|
||||
}: {
|
||||
replyTo: ChatBskyConvoDefs.MessageView
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
const convo = useConvoActive()
|
||||
const {clearReply} = useMessageReplies()
|
||||
const getReplyPreviewText = useReplyPreviewText()
|
||||
const {text, subtle} = getReplyPreviewText(replyTo)
|
||||
|
||||
const onRemove = () => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
clearReply()
|
||||
@@ -35,19 +48,6 @@ export function MessageInputReply() {
|
||||
? createSanitizedDisplayName(senderProfile, false)
|
||||
: null
|
||||
|
||||
let text = replyTo.text
|
||||
let subtle = false
|
||||
if (!text.trim()) {
|
||||
subtle = true
|
||||
if (ChatBskyEmbedJoinLink.isView(replyTo.embed)) {
|
||||
text = l`(chat invite link)`
|
||||
} else if (AppBskyEmbedRecord.isView(replyTo.embed)) {
|
||||
text = l`(contains embedded content)`
|
||||
} else {
|
||||
text = l`No text`
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
@@ -82,7 +82,7 @@ export function MessageInputReply() {
|
||||
<Button
|
||||
label={l`Cancel reply`}
|
||||
onPress={onRemove}
|
||||
style={[a.px_2xs]}
|
||||
style={[a.px_2xs, {transform: [{translateX: 2}]}]}
|
||||
hitSlop={HITSLOP_20}>
|
||||
<XIcon size="xs" style={t.atoms.text_contrast_high} />
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user