From ed67641dd52db5c9d3a95d5909b2f6760b284fa0 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Wed, 3 Jun 2026 00:36:54 -0400 Subject: [PATCH] fix: media gallery encrypted rooms + Ctrl+K double-menu - MediaGallery: switch from createMessagesRequest (returns raw encrypted events) to room.getLiveTimeline().getEvents() which gives already- decrypted MatrixEvent objects. Load More uses paginateEventTimeline(). - QuickSwitcher: change hotkey from Ctrl+K to Ctrl+P to avoid conflict with the existing SearchModalRenderer mod+k handler Co-Authored-By: Claude Sonnet 4.6 --- src/app/features/room/MediaGallery.tsx | 74 +++++++++----------- src/app/pages/client/ClientNonUIFeatures.tsx | 2 +- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/src/app/features/room/MediaGallery.tsx b/src/app/features/room/MediaGallery.tsx index c008854d2..0b5ffa055 100644 --- a/src/app/features/room/MediaGallery.tsx +++ b/src/app/features/room/MediaGallery.tsx @@ -13,7 +13,7 @@ import { TooltipProvider, config, } from 'folds'; -import { Direction, EventType, MatrixEvent, MsgType, Room } from 'matrix-js-sdk'; +import { EventType, MsgType, Room } from 'matrix-js-sdk'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { mxcUrlToHttp } from '../../utils/matrix'; @@ -65,53 +65,45 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) { const useAuthentication = useMediaAuthentication(); const [tab, setTab] = useState('image'); - const [events, setEvents] = useState([]); const [loading, setLoading] = useState(false); - const [paginationToken, setPaginationToken] = useState(null); + const [canLoadMore, setCanLoadMore] = useState(true); const msgtype = TAB_MSGTYPES[tab]; - const loadMedia = useCallback( - async (fromToken: string | null, append: boolean) => { - setLoading(true); - try { - const response = await mx.createMessagesRequest( - room.roomId, - fromToken, - 100, - Direction.Backward, - undefined, - ); - const { end, chunk } = response; - const filtered = chunk - .filter( - (ev) => - ev.type === EventType.RoomMessage && - ev.content?.msgtype === msgtype && - !ev.unsigned?.redacted_because, - ) - .map((ev) => new MatrixEvent(ev)); + // Read already-decrypted events from the live timeline (works for E2EE rooms) + const getFilteredEvents = useCallback(() => { + const timeline = room.getLiveTimeline(); + return timeline + .getEvents() + .filter((ev) => { + if (ev.isRedacted()) return false; + const content = ev.getContent(); + return ev.getType() === EventType.RoomMessage && content.msgtype === msgtype; + }) + .slice() + .reverse(); // newest first + }, [room, msgtype]); - setEvents((prev) => (append ? [...prev, ...filtered] : filtered)); - setPaginationToken(end ?? null); - } catch { - // silently swallow fetch errors — gallery stays showing what it has - } finally { - setLoading(false); - } - }, - [mx, room.roomId, msgtype], - ); + const [events, setEvents] = useState(() => getFilteredEvents()); useEffect(() => { - setEvents([]); - setPaginationToken(null); - loadMedia(null, false).catch(() => undefined); - }, [loadMedia]); + setEvents(getFilteredEvents()); + setCanLoadMore(true); + }, [getFilteredEvents]); - const handleLoadMore = () => { - if (paginationToken) loadMedia(paginationToken, true).catch(() => undefined); - }; + const handleLoadMore = useCallback(async () => { + setLoading(true); + try { + const timeline = room.getLiveTimeline(); + const hasMore = await mx.paginateEventTimeline(timeline, { backwards: true, limit: 100 }); + setEvents(getFilteredEvents()); + setCanLoadMore(hasMore); + } catch { + // silently swallow + } finally { + setLoading(false); + } + }, [mx, room, getFilteredEvents]); return ( 0 && (