Add relatedProfiles to convo

This commit is contained in:
Samuel Newman
2026-04-24 11:34:15 +03:00
parent a9b4a35d23
commit bc085d935b
8 changed files with 102 additions and 39 deletions
+7 -1
View File
@@ -4,23 +4,29 @@ import {useLingui} from '@lingui/react/macro'
import {atoms as a} from '#/alf'
import {MessageContextMenu} from '#/components/dms/MessageContextMenu'
import type * as bsky from '#/types/bsky'
export function ActionsWrapper({
message,
isFromSelf,
senderProfile,
children,
onTap,
}: {
message: ChatBskyConvoDefs.MessageView
hasReactions?: boolean
isFromSelf: boolean
senderProfile?: bsky.profile.AnyProfileView
children: React.ReactNode
onTap?: () => void
}) {
const {t: l} = useLingui()
return (
<MessageContextMenu message={message} onTap={onTap}>
<MessageContextMenu
message={message}
senderProfile={senderProfile}
onTap={onTap}>
{trigger =>
// will always be true, since this file is platform split
trigger.IS_NATIVE && (
+4 -1
View File
@@ -10,6 +10,7 @@ import {MessageContextMenu} from '#/components/dms/MessageContextMenu'
import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontalIcon} from '#/components/icons/DotGrid'
import {EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji'
import * as Toast from '#/components/Toast'
import type * as bsky from '#/types/bsky'
import {EmojiReactionPicker} from './EmojiReactionPicker'
import {hasReachedReactionLimit} from './util'
@@ -17,12 +18,14 @@ export function ActionsWrapper({
message,
hasReactions,
isFromSelf,
senderProfile,
children,
onTap,
}: {
message: ChatBskyConvoDefs.MessageView
hasReactions?: boolean
isFromSelf: boolean
senderProfile?: bsky.profile.AnyProfileView
children: React.ReactNode
onTap?: () => void
}) {
@@ -114,7 +117,7 @@ export function ActionsWrapper({
)
}}
</EmojiReactionPicker>
<MessageContextMenu message={message}>
<MessageContextMenu message={message} senderProfile={senderProfile}>
{({props, state, IS_NATIVE, control}) => {
// always false, file is platform split
if (IS_NATIVE) return null
+4 -3
View File
@@ -25,15 +25,18 @@ import {usePromptControl} from '#/components/Prompt'
import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env'
import type * as bsky from '#/types/bsky'
import {EmojiReactionPicker} from './EmojiReactionPicker'
import {hasReachedReactionLimit} from './util'
export let MessageContextMenu = ({
message,
senderProfile,
children,
onTap,
}: {
message: ChatBskyConvoDefs.MessageView
senderProfile?: bsky.profile.AnyProfileView
children: TriggerProps['children']
onTap?: () => void
}): React.ReactNode => {
@@ -110,9 +113,7 @@ export let MessageContextMenu = ({
[l, convo, message, currentAccount?.did],
)
const sender = convo.convo.members.find(
member => member.did === message.sender.did,
)
const sender = senderProfile
return (
<>
+14 -10
View File
@@ -30,7 +30,6 @@ import {useQueryClient} from '@tanstack/react-query'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {makeProfileLink} from '#/lib/routes/links'
import {useConvoActive} from '#/state/messages/convo'
import {type ConvoItem} from '#/state/messages/convo/types'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache'
@@ -43,7 +42,6 @@ import {InlineLinkText, Link} from '#/components/Link'
import * as ProfileCard from '#/components/ProfileCard'
import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky'
import {DateDivider} from './DateDivider'
import {useDateDividerToggle} from './DateDividerToggle'
import {MessageItemEmbed} from './MessageItemEmbed'
@@ -93,19 +91,20 @@ function isWithinClusterBoundary({
let MessageItem = ({
item,
isGroupChat = false,
profile,
}: {
item: ConvoItem & {type: 'message' | 'pending-message'}
isGroupChat?: boolean
profile?: bsky.profile.AnyProfileView
}): React.ReactNode => {
const t = useTheme()
const {currentAccount} = useSession()
const {t: l} = useLingui()
const {convo} = useConvoActive()
const moderationOpts = useModerationOpts()
const queryClient = useQueryClient()
const profile = item.relatedProfiles.find(
p => p.did === item.message.sender.did,
)
const reactionsControl = useDialogControl()
const reactionTapRef = useRef(false)
@@ -277,9 +276,7 @@ let MessageItem = ({
return l`You reacted ${reaction.value}`
} else {
const senderDid = reaction.sender.did
const memberSender = convo.members.find(
member => member.did === senderDid,
)
const memberSender = item.relatedProfiles.find(p => p.did === senderDid)
if (memberSender) {
return l`${createSanitizedDisplayName(memberSender)} reacted ${reaction.value}`
}
@@ -290,7 +287,13 @@ let MessageItem = ({
one: '# person',
other: '# people',
})} reacted ${groupedReactions.map(g => g.value).join(' ')}`
}, [reactions, groupedReactions, currentAccount?.did, convo.members, l])
}, [
reactions,
groupedReactions,
currentAccount?.did,
item.relatedProfiles,
l,
])
const appliedReactions = (
<LayoutAnimationConfig skipEntering skipExiting>
@@ -375,7 +378,7 @@ let MessageItem = ({
) : null}
<ReactionsDialog
control={reactionsControl}
members={convo.members}
members={item.relatedProfiles}
message={message}
reactions={message.reactions}
groupedReactions={groupedReactions}
@@ -436,6 +439,7 @@ let MessageItem = ({
hasReactions={hasReactions}
isFromSelf={isFromSelf}
message={message}
senderProfile={profile}
onTap={() => {
if (reactionTapRef.current) return
if (!hasLargeGapFromPrev) {
+4 -3
View File
@@ -1,4 +1,4 @@
import {type ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api'
import {ChatBskyConvoDefs} from '@atproto/api'
import {type MessageDescriptor} from '@lingui/core'
import {msg} from '@lingui/core/macro'
@@ -15,6 +15,7 @@ import {
Unlock_Stroke2_Corner2_Rounded as UnlockIcon,
} from '#/components/icons/Lock'
import {PencilLine_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil'
import type * as bsky from '#/types/bsky'
export type SystemMessageInfo = {
message: MessageDescriptor
@@ -23,7 +24,7 @@ export type SystemMessageInfo = {
function getReferredDisplayName(
user: ChatBskyConvoDefs.SystemMessageReferredUser,
relatedProfiles: ChatBskyActorDefs.ProfileViewBasic[],
relatedProfiles: bsky.profile.AnyProfileView[],
): string | null {
const profile = relatedProfiles.find(p => p.did === user.did)
return profile ? createSanitizedDisplayName(profile) : null
@@ -31,7 +32,7 @@ function getReferredDisplayName(
export function getSystemMessageInfo(
data: ChatBskyConvoDefs.SystemMessageView['data'],
relatedProfiles: ChatBskyActorDefs.ProfileViewBasic[],
relatedProfiles: bsky.profile.AnyProfileView[],
): SystemMessageInfo | null {
if (ChatBskyConvoDefs.isSystemMessageDataAddMember(data)) {
const name = getReferredDisplayName(data.member, relatedProfiles)
@@ -374,9 +374,6 @@ export function MessagesList({
return (
<MessageItem
item={item}
profile={convoState.convo.members.find(
member => member.did === item.message.sender.did,
)}
isGroupChat={convoState.convo.kind === 'group'}
/>
)
+64 -16
View File
@@ -43,6 +43,7 @@ import {
parseConvoView,
} from '#/components/dms/util'
import {IS_NATIVE} from '#/env'
import type * as bsky from '#/types/bsky'
const logger = Logger.create(Logger.Context.ConversationAgent)
@@ -107,10 +108,7 @@ export class Convo {
{id: string; message: ChatBskyConvoSendMessage.InputSchema['message']}
> = new Map()
private deletedMessages: Set<string> = new Set()
private systemMessageProfiles: Map<
string,
ChatBskyActorDefs.ProfileViewBasic
> = new Map()
private relatedProfiles: Map<string, bsky.profile.AnyProfileView> = new Map()
private isProcessingPendingMessages = false
@@ -483,7 +481,7 @@ export class Convo {
this.newMessages = new Map()
this.pendingMessages = new Map()
this.deletedMessages = new Set()
this.systemMessageProfiles = new Map()
this.relatedProfiles = new Map()
this.pendingMessageFailure = null
this.fetchMessageHistoryError = undefined
@@ -510,6 +508,11 @@ export class Convo {
private setConvo(convo: ChatBskyConvoDefs.ConvoView) {
this.convo = parseConvoView(convo, this.senderUserDid) ?? this.convo
if (this.convo) {
for (const member of this.convo.members) {
this.relatedProfiles.set(member.did, member)
}
}
}
private updateConvo(convo: Partial<ChatBskyConvoDefs.ConvoView>) {
@@ -517,6 +520,9 @@ export class Convo {
this.convo =
parseConvoView({...this.convo.view, ...convo}, this.senderUserDid) ??
this.convo
for (const member of this.convo.members) {
this.relatedProfiles.set(member.did, member)
}
}
}
@@ -704,7 +710,7 @@ export class Convo {
if (relatedProfiles) {
for (const profile of relatedProfiles) {
this.systemMessageProfiles.set(profile.did, profile)
this.relatedProfiles.set(profile.did, profile)
}
}
@@ -824,6 +830,12 @@ export class Convo {
*/
this.latestRev = ev.rev
if ('relatedProfiles' in ev && Array.isArray(ev.relatedProfiles)) {
for (const profile of ev.relatedProfiles) {
this.relatedProfiles.set(profile.did, profile)
}
}
if (
ChatBskyConvoDefs.isLogCreateMessage(ev) &&
ChatBskyConvoDefs.isMessageView(ev.message)
@@ -876,14 +888,6 @@ export class Convo {
const systemView = toSystemMessageView(ev)
if (systemView) {
this.newMessages.set(systemView.id, systemView)
if (
'relatedProfiles' in ev &&
Array.isArray(ev.relatedProfiles)
) {
for (const profile of ev.relatedProfiles) {
this.systemMessageProfiles.set(profile.did, profile)
}
}
needsCommit = true
}
}
@@ -1168,6 +1172,44 @@ export class Convo {
}
}
private getRelatedProfilesForItem(
m:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| ChatBskyConvoDefs.SystemMessageView,
): bsky.profile.AnyProfileView[] {
const seen = new Set<string>()
const profiles: bsky.profile.AnyProfileView[] = []
const add = (did: string) => {
if (seen.has(did)) return
seen.add(did)
const p = this.relatedProfiles.get(did)
if (p) profiles.push(p)
}
if (ChatBskyConvoDefs.isMessageView(m)) {
add(m.sender.did)
if (m.reactions) {
for (const reaction of m.reactions) {
add(reaction.sender.did)
}
}
} else if (ChatBskyConvoDefs.isDeletedMessageView(m)) {
add(m.sender.did)
} else if (ChatBskyConvoDefs.isSystemMessageView(m)) {
const data = m.data
if ('member' in data && data.member?.did) {
add(data.member.did)
}
if ('addedBy' in data && data.addedBy?.did) {
add(data.addedBy.did)
}
}
return profiles
}
/*
* Items in reverse order, since FlatList inverts
*/
@@ -1180,6 +1222,7 @@ export class Convo {
type: 'message',
key: m.id,
message: m,
relatedProfiles: this.getRelatedProfilesForItem(m),
nextMessage: null,
prevMessage: null,
})
@@ -1188,6 +1231,7 @@ export class Convo {
type: 'deleted-message',
key: m.id,
message: m,
relatedProfiles: this.getRelatedProfilesForItem(m),
nextMessage: null,
prevMessage: null,
})
@@ -1196,7 +1240,7 @@ export class Convo {
type: 'system-message',
key: m.id,
message: m,
relatedProfiles: Array.from(this.systemMessageProfiles.values()),
relatedProfiles: this.getRelatedProfilesForItem(m),
})
}
})
@@ -1218,6 +1262,7 @@ export class Convo {
type: 'message',
key: m.id,
message: m,
relatedProfiles: this.getRelatedProfilesForItem(m),
nextMessage: null,
prevMessage: null,
})
@@ -1226,6 +1271,7 @@ export class Convo {
type: 'deleted-message',
key: m.id,
message: m,
relatedProfiles: this.getRelatedProfilesForItem(m),
nextMessage: null,
prevMessage: null,
})
@@ -1234,12 +1280,13 @@ export class Convo {
type: 'system-message',
key: m.id,
message: m,
relatedProfiles: Array.from(this.systemMessageProfiles.values()),
relatedProfiles: this.getRelatedProfilesForItem(m),
})
}
})
this.pendingMessages.forEach(m => {
const senderProfile = this.relatedProfiles.get(this.senderUserDid)
items.push({
type: 'pending-message',
key: m.id,
@@ -1255,6 +1302,7 @@ export class Convo {
did: this.senderUserDid,
},
},
relatedProfiles: senderProfile ? [senderProfile] : [],
nextMessage: null,
prevMessage: null,
failed: this.pendingMessageFailure !== null,
+5 -2
View File
@@ -1,12 +1,12 @@
import {
type BskyAgent,
type ChatBskyActorDefs,
type ChatBskyConvoDefs,
type ChatBskyConvoSendMessage,
} from '@atproto/api'
import {type MessagesEventBus} from '#/state/messages/events/agent'
import {type ConvoWithDetails} from '#/components/dms/util'
import type * as bsky from '#/types/bsky'
export type ConvoParams = {
convoId: string
@@ -87,6 +87,7 @@ export type ConvoItem =
type: 'message'
key: string
message: ChatBskyConvoDefs.MessageView
relatedProfiles: bsky.profile.AnyProfileView[]
nextMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
@@ -100,6 +101,7 @@ export type ConvoItem =
type: 'pending-message'
key: string
message: ChatBskyConvoDefs.MessageView
relatedProfiles: bsky.profile.AnyProfileView[]
nextMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
@@ -118,6 +120,7 @@ export type ConvoItem =
type: 'deleted-message'
key: string
message: ChatBskyConvoDefs.DeletedMessageView
relatedProfiles: bsky.profile.AnyProfileView[]
nextMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
@@ -131,7 +134,7 @@ export type ConvoItem =
type: 'system-message'
key: string
message: ChatBskyConvoDefs.SystemMessageView
relatedProfiles: ChatBskyActorDefs.ProfileViewBasic[]
relatedProfiles: bsky.profile.AnyProfileView[]
}
| {
type: 'error'