feat(media): Media Gallery includes media posted in threads (#165)
CI / Build & Quality Checks (push) Successful in 4m26s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 16s
CI / Trigger Desktop Build (push) Successful in 7s
CI / Playwright smoke (e2e) (push) Successful in 12m0s
CI / Build & Quality Checks (push) Successful in 4m26s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 16s
CI / Trigger Desktop Build (push) Successful in 7s
CI / Playwright smoke (e2e) (push) Successful in 12m0s
The SDK routes thread replies out of every room timeline set, the gallery's detached one included, into the room's Thread objects, so photos posted in a thread never reached the gallery. The gallery now merges media from the loaded threads (deduped, newest first) and refreshes on ThreadEvent.NewReply, waiting for decryption in encrypted rooms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a8db61f79f
commit
ba48e95993
@@ -9,6 +9,7 @@ import {
|
||||
Room,
|
||||
RoomEvent,
|
||||
RoomEventHandlerMap,
|
||||
ThreadEvent,
|
||||
} from 'matrix-js-sdk';
|
||||
|
||||
import {
|
||||
@@ -34,7 +35,7 @@ export const isMediaMessage = (event: MatrixEvent): boolean =>
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
export type RoomMediaTimeline = {
|
||||
/** Every loaded media message in the room, newest first. */
|
||||
/** Every loaded media message in the room and its threads, newest first. */
|
||||
events: MatrixEvent[];
|
||||
loadMore: () => Promise<void>;
|
||||
loading: boolean;
|
||||
@@ -57,11 +58,23 @@ export const useRoomMediaTimeline = (mx: MatrixClient, room: Room): RoomMediaTim
|
||||
[mx, room],
|
||||
);
|
||||
|
||||
const readEvents = useCallback(
|
||||
(): MatrixEvent[] =>
|
||||
collectTimelineEvents(detached.set.getLiveTimeline()).filter(isMediaMessage).reverse(),
|
||||
[detached],
|
||||
);
|
||||
// [Gitea #165] The SDK routes thread replies out of every room timeline set
|
||||
// (ours included) into the room's Thread objects, so media posted in a
|
||||
// thread only shows up if we read the loaded threads too.
|
||||
const readEvents = useCallback((): MatrixEvent[] => {
|
||||
const seen = new Set<string>();
|
||||
return [
|
||||
...collectTimelineEvents(detached.set.getLiveTimeline()),
|
||||
...room.getThreads().flatMap((thread) => thread.timeline),
|
||||
]
|
||||
.filter((event) => {
|
||||
const id = event.getId();
|
||||
if (!id || seen.has(id) || !isMediaMessage(event)) return false;
|
||||
seen.add(id);
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => b.getTs() - a.getTs());
|
||||
}, [detached, room]);
|
||||
|
||||
const [events, setEvents] = useState<MatrixEvent[]>(readEvents);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -119,11 +132,23 @@ export const useRoomMediaTimeline = (mx: MatrixClient, room: Room): RoomMediaTim
|
||||
refresh();
|
||||
};
|
||||
|
||||
const onThreadReply = (_thread: unknown, event: MatrixEvent) => {
|
||||
if (event.isBeingDecrypted() || event.shouldAttemptDecryption()) {
|
||||
event.once(MatrixEventEvent.Decrypted, () => {
|
||||
if (isMediaMessage(event)) refresh();
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (isMediaMessage(event)) refresh();
|
||||
};
|
||||
|
||||
room.on(RoomEvent.Timeline, onTimeline);
|
||||
room.on(RoomEvent.Redaction, onRedaction);
|
||||
room.on(ThreadEvent.NewReply, onThreadReply);
|
||||
return () => {
|
||||
room.removeListener(RoomEvent.Timeline, onTimeline);
|
||||
room.removeListener(RoomEvent.Redaction, onRedaction);
|
||||
room.removeListener(ThreadEvent.NewReply, onThreadReply);
|
||||
};
|
||||
}, [detached, room, readEvents]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user