Process events list client side
This commit is contained in:
@@ -48,7 +48,7 @@ impl APIEvent {
|
||||
pub struct APIEventsList {
|
||||
pub start: String,
|
||||
pub end: Option<String>,
|
||||
pub messages: Vec<APIEvent>,
|
||||
pub events: Vec<APIEvent>,
|
||||
}
|
||||
|
||||
/// Get messages for a given room
|
||||
@@ -65,7 +65,7 @@ pub(super) async fn get_events(
|
||||
Ok(APIEventsList {
|
||||
start: messages.start,
|
||||
end: messages.end,
|
||||
messages: stream::iter(messages.chunk)
|
||||
events: stream::iter(messages.chunk)
|
||||
.then(async |msg| APIEvent::from_evt(msg, room.room_id()).await)
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
|
||||
@@ -52,7 +52,7 @@ impl APIRoomInfo {
|
||||
is_space: r.is_space(),
|
||||
parents: parent_spaces,
|
||||
number_unread_messages: r.unread_notification_counts().notification_count,
|
||||
latest_event: get_events(r, 1, None).await?.messages.into_iter().next(),
|
||||
latest_event: get_events(r, 1, None).await?.events.into_iter().next(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,70 @@
|
||||
import { APIClient } from "../ApiClient";
|
||||
import type { Room } from "./MatrixApiRoom";
|
||||
|
||||
export interface MatrixRoomMessage {
|
||||
type: "m.room.message";
|
||||
content: {
|
||||
body: string;
|
||||
msgtype: "m.text" | "m.image" | string;
|
||||
"m.relates_to"?: {
|
||||
event_id: string;
|
||||
rel_type: "m.replace" | string;
|
||||
};
|
||||
file?: {
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface MatrixReaction {
|
||||
type: "m.reaction";
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
event_id: string;
|
||||
key: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface MatrixRoomRedaction {
|
||||
type: "m.room.redaction";
|
||||
redacts: string;
|
||||
}
|
||||
|
||||
export type MatrixEventData =
|
||||
| MatrixRoomMessage
|
||||
| MatrixReaction
|
||||
| MatrixRoomRedaction
|
||||
| { type: "other" };
|
||||
|
||||
export interface MatrixEvent {
|
||||
id: string;
|
||||
time: number;
|
||||
sender: string;
|
||||
data: MatrixEventData;
|
||||
}
|
||||
|
||||
export interface MatrixEventsList {
|
||||
start: string;
|
||||
end?: string;
|
||||
events: MatrixEvent[];
|
||||
}
|
||||
|
||||
export class MatrixApiEvent {
|
||||
/**
|
||||
* Get Matrix room events
|
||||
*/
|
||||
static async GetRoomEvents(
|
||||
room: Room,
|
||||
from?: string
|
||||
): Promise<MatrixEventsList> {
|
||||
return (
|
||||
await APIClient.exec({
|
||||
method: "GET",
|
||||
uri:
|
||||
`/matrix/room/${encodeURIComponent(room.id)}/events` +
|
||||
(from ? `?from=${from}` : ""),
|
||||
})
|
||||
).data;
|
||||
}
|
||||
}
|
||||
|
||||
101
matrixgw_frontend/src/utils/RoomEventsManager.ts
Normal file
101
matrixgw_frontend/src/utils/RoomEventsManager.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type {
|
||||
MatrixEvent,
|
||||
MatrixEventsList,
|
||||
} from "../api/matrix/MatrixApiEvent";
|
||||
import type { Room } from "../api/matrix/MatrixApiRoom";
|
||||
|
||||
export interface MessageReaction {
|
||||
event_id: string;
|
||||
account: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
event_id: string;
|
||||
sent: number;
|
||||
modified: boolean;
|
||||
reactions: MessageReaction[];
|
||||
content: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
export class RoomEventsManager {
|
||||
readonly room: Room;
|
||||
private events: MatrixEvent[];
|
||||
messages: Message[];
|
||||
endToken?: string;
|
||||
|
||||
constructor(room: Room, initialMessages: MatrixEventsList) {
|
||||
this.room = room;
|
||||
this.events = [];
|
||||
this.messages = [];
|
||||
|
||||
this.processNewEvents(initialMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process events given by the API
|
||||
*/
|
||||
processNewEvents(evts: MatrixEventsList) {
|
||||
this.endToken = evts.end;
|
||||
this.events = [...this.events, ...evts.events];
|
||||
this.rebuildMessagesList();
|
||||
}
|
||||
|
||||
private rebuildMessagesList() {
|
||||
// Sorts events list to process oldest events first
|
||||
this.events.sort((a, b) => a.time - b.time);
|
||||
|
||||
// First, process redactions to skip redacted events
|
||||
let redacted = new Set(
|
||||
this.events
|
||||
.map((e) =>
|
||||
e.data.type === "m.room.redaction" ? e.data.redacts : undefined
|
||||
)
|
||||
.filter((e) => e !== undefined)
|
||||
);
|
||||
|
||||
for (const evt of this.events) {
|
||||
if (redacted.has(evt.id)) continue;
|
||||
|
||||
const data = evt.data;
|
||||
|
||||
// Message
|
||||
if (data.type === "m.room.message") {
|
||||
// Check if this message replaces another one
|
||||
if (data.content["m.relates_to"]) {
|
||||
const message = this.messages.find(
|
||||
(m) => m.event_id === data.content["m.relates_to"]?.event_id
|
||||
);
|
||||
if (!message) continue;
|
||||
message.modified = true;
|
||||
message.content = data.content.body;
|
||||
continue;
|
||||
}
|
||||
|
||||
this.messages.push({
|
||||
event_id: evt.id,
|
||||
modified: false,
|
||||
reactions: [],
|
||||
sent: evt.time,
|
||||
image: data.content.file?.url,
|
||||
content: data.content.body,
|
||||
});
|
||||
}
|
||||
|
||||
// Reaction
|
||||
if (data.type === "m.reaction") {
|
||||
const message = this.messages.find(
|
||||
(m) => m.event_id === data.content["m.relates_to"].event_id
|
||||
);
|
||||
|
||||
if (!message) continue;
|
||||
message.reactions.push({
|
||||
account: evt.sender,
|
||||
event_id: evt.id,
|
||||
key: data.content["m.relates_to"].key,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,30 @@
|
||||
import React from "react";
|
||||
import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent";
|
||||
import type { UsersMap } from "../../api/matrix/MatrixApiProfile";
|
||||
import type { Room } from "../../api/matrix/MatrixApiRoom";
|
||||
import { RoomEventsManager } from "../../utils/RoomEventsManager";
|
||||
import { AsyncWidget } from "../AsyncWidget";
|
||||
|
||||
export function RoomWidget(p: {
|
||||
room: Room;
|
||||
users: UsersMap;
|
||||
}): React.ReactElement {
|
||||
return <>room</>;
|
||||
const [roomMgr, setRoomMgr] = React.useState<undefined | RoomEventsManager>();
|
||||
|
||||
const load = async () => {
|
||||
setRoomMgr(undefined);
|
||||
const messages = await MatrixApiEvent.GetRoomEvents(p.room);
|
||||
const mgr = new RoomEventsManager(p.room, messages);
|
||||
setRoomMgr(mgr);
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncWidget
|
||||
loadKey={p.room.id}
|
||||
ready={!!roomMgr}
|
||||
load={load}
|
||||
errMsg="Failed to load room!"
|
||||
build={() => <>room</>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user