feat(media): timeline images open the gallery lightbox at that event (#219)
CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s

Clicking an image in the room or thread timeline now opens the same viewer
the media gallery uses — dark backdrop, sender + date, 1/N counter, ←/→ across
the room's media, +/-/0 and wheel/double-click zoom, download, jump to
message — positioned at the clicked event. RoomMediaLightbox feeds it from the
detached media timeline (#163); when the event isn't in the loaded window it
pages back (bounded, 6 pages) and shows the clicked image alone meanwhile, so
the viewer opens instantly. ImageContent gains onOpenViewer (RenderMessageContent
passes onOpenImageViewer); its built-in viewer remains for stickers, search
results, pins, notifications and avatars.

Verified headless: click → 'Media viewer' dialog focused, counter 1/2, '+' →
120 %, Jump to message present, Esc closes; an older image 10 pages up →
found at 10/45 after paging.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-09-19 13:13:28 -04:00
co-authored by Claude Opus 5
parent 8b1c9fa610
commit f111b3c9af
6 changed files with 137 additions and 25 deletions
+12
View File
@@ -115,6 +115,7 @@ import { RetentionContent, isExpired } from '../../utils/retention';
import { useKeyDown } from '../../hooks/useKeyDown';
import { useDocumentFocusChange } from '../../hooks/useDocumentFocusChange';
import { RenderMessageContent } from '../../components/RenderMessageContent';
import { RoomMediaLightbox } from './RoomMediaLightbox';
import { Image } from '../../components/media';
import { ImageViewer } from '../../components/image-viewer';
import { roomToParentsAtom } from '../../state/room/roomToParents';
@@ -523,6 +524,8 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const canPinEvent = permissions.stateEvent(StateEvent.RoomPinnedEvents, mx.getSafeUserId());
const [editId, setEditId] = useState<string>();
const [editHistoryEvent, setEditHistoryEvent] = useState<MatrixEvent | undefined>();
// [Gitea #219] Timeline images open the shared media lightbox at that event.
const [lightboxEventId, setLightboxEventId] = useState<string | undefined>();
const roomToParents = useAtomValue(roomToParentsAtom);
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
@@ -1231,6 +1234,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
linkifyOpts={linkifyOpts}
outlineAttachment={messageLayout === MessageLayout.Bubble}
eventId={mEventId}
onOpenImageViewer={() => setLightboxEventId(mEventId)}
/>
)}
</Message>
@@ -1360,6 +1364,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
linkifyOpts={linkifyOpts}
outlineAttachment={messageLayout === MessageLayout.Bubble}
eventId={mEventId}
onOpenImageViewer={() => setLightboxEventId(mEventId)}
/>
);
}
@@ -2317,6 +2322,13 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
onClose={() => setEditHistoryEvent(undefined)}
/>
)}
{lightboxEventId && (
<RoomMediaLightbox
room={room}
eventId={lightboxEventId}
onClose={() => setLightboxEventId(undefined)}
/>
)}
</>
);
}