mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 01:21:09 +02:00
feat(matrix-web): add read receipt indicators
- Add ReadReceipt type for tracking who read messages - Track read receipts via Matrix SDK receipt events - Display single checkmark for sent messages - Display blue double checkmarks when message is read - Tooltip shows names of readers - Auto-refresh timeline when receipts are received Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
84fca4036e
commit
840f6d7ff3
3 changed files with 85 additions and 4 deletions
|
|
@ -16,6 +16,8 @@
|
|||
Lock,
|
||||
Warning,
|
||||
Smiley,
|
||||
Check,
|
||||
Checks,
|
||||
} from '@manacore/shared-icons';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -486,11 +488,27 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Time (shown on hover) -->
|
||||
<!-- Time and read status -->
|
||||
<div
|
||||
class="flex items-center gap-2 mt-1.5 px-1 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
class="flex items-center gap-1.5 mt-1.5 px-1 {message.isOwn ? 'justify-end' : ''}"
|
||||
>
|
||||
<span class="text-xs text-muted-foreground">{formattedTime()}</span>
|
||||
<span class="text-xs text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">{formattedTime()}</span>
|
||||
<!-- Read receipt indicator (for own messages) -->
|
||||
{#if message.isOwn}
|
||||
{#if message.readBy && message.readBy.length > 0}
|
||||
<Checks
|
||||
class="h-4 w-4 text-blue-500"
|
||||
weight="bold"
|
||||
title="Gelesen von: {message.readBy.map(r => r.userName).join(', ')}"
|
||||
/>
|
||||
{:else}
|
||||
<Check
|
||||
class="h-4 w-4 text-muted-foreground/50"
|
||||
weight="bold"
|
||||
title="Gesendet"
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Message actions (hover) -->
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import type {
|
|||
SimpleMessage,
|
||||
MessageType,
|
||||
MessageReaction,
|
||||
ReadReceipt,
|
||||
RoomMember,
|
||||
VerificationStatus,
|
||||
DeviceInfo,
|
||||
|
|
@ -300,6 +301,14 @@ class MatrixStore {
|
|||
// Also trigger room list update for DMs
|
||||
this._rooms = this._client!.getRooms();
|
||||
});
|
||||
|
||||
// Read receipt updates
|
||||
this._client.on(sdk.RoomEvent.Receipt, (event, room) => {
|
||||
// Update timeline if we're in this room to refresh read receipts
|
||||
if (room.roomId === this._currentRoomId) {
|
||||
this._timeline = [...(room.getLiveTimeline().getEvents() || [])];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1328,6 +1337,10 @@ class MatrixStore {
|
|||
// Collect reactions for this message
|
||||
const reactions = this.getReactionsForEvent(event.getId() || '', timeline);
|
||||
|
||||
// Get read receipts for this message (only for own messages)
|
||||
const isOwn = event.getSender() === this._client?.getUserId();
|
||||
const readBy = isOwn ? this.getReadReceiptsForEvent(event) : undefined;
|
||||
|
||||
return {
|
||||
id: event.getId() || '',
|
||||
sender: event.getSender() || '',
|
||||
|
|
@ -1336,13 +1349,14 @@ class MatrixStore {
|
|||
formattedBody: content.formatted_body,
|
||||
timestamp: event.getTs(),
|
||||
type: msgtype as MessageType,
|
||||
isOwn: event.getSender() === this._client?.getUserId(),
|
||||
isOwn,
|
||||
replyTo: replyToId,
|
||||
replyToBody,
|
||||
edited: !!event.replacingEvent(),
|
||||
redacted: isRedacted,
|
||||
media,
|
||||
reactions: reactions.length > 0 ? reactions : undefined,
|
||||
readBy: readBy && readBy.length > 0 ? readBy : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1398,6 +1412,44 @@ class MatrixStore {
|
|||
return reactions.sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get read receipts for a specific event
|
||||
*/
|
||||
private getReadReceiptsForEvent(event: MatrixEvent): ReadReceipt[] {
|
||||
const eventId = event.getId();
|
||||
const roomId = event.getRoomId();
|
||||
if (!eventId || !roomId || !this._client) return [];
|
||||
|
||||
const room = this._client.getRoom(roomId);
|
||||
if (!room) return [];
|
||||
|
||||
const myUserId = this._client.getUserId();
|
||||
const receipts: ReadReceipt[] = [];
|
||||
|
||||
// Get all members who have read up to or past this event
|
||||
const members = room.getJoinedMembers();
|
||||
for (const member of members) {
|
||||
// Skip self
|
||||
if (member.userId === myUserId) continue;
|
||||
|
||||
// Get the user's read receipt
|
||||
const receiptEvent = room.getEventReadUpTo(member.userId);
|
||||
if (!receiptEvent) continue;
|
||||
|
||||
// Check if their read receipt is at or after this event
|
||||
const receiptEventObj = room.findEventById(receiptEvent);
|
||||
if (receiptEventObj && receiptEventObj.getTs() >= event.getTs()) {
|
||||
receipts.push({
|
||||
userId: member.userId,
|
||||
userName: member.name || member.userId.split(':')[0].substring(1),
|
||||
timestamp: receiptEventObj.getTs(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return receipts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display name for message sender
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -39,6 +39,15 @@ export interface MessageReaction {
|
|||
includesMe: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read receipt info for a message
|
||||
*/
|
||||
export interface ReadReceipt {
|
||||
userId: string;
|
||||
userName: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified message for UI rendering
|
||||
*/
|
||||
|
|
@ -57,6 +66,8 @@ export interface SimpleMessage {
|
|||
redacted?: boolean;
|
||||
media?: MediaInfo;
|
||||
reactions?: MessageReaction[];
|
||||
// Read receipts
|
||||
readBy?: ReadReceipt[];
|
||||
}
|
||||
|
||||
export type MessageType =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue