diff --git a/src/App.svelte b/src/App.svelte
index b12d3da..e5ef036 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -15,6 +15,7 @@
import ChannelDialog from './ui/ChannelDialog.svelte';
import PublicServers from './ui/PublicServers.svelte';
import ServerInfoDialog from './ui/ServerInfoDialog.svelte';
+ import ImageViewer from './ui/ImageViewer.svelte';
import ShareDialog from './ui/ShareDialog.svelte';
import { share } from './lib/share.svelte.ts';
import ResizeHandle from './ui/ResizeHandle.svelte';
@@ -63,13 +64,24 @@
else ui.panelOpen = false;
}
+ // Images in rendered text (messages, descriptions, the quick preview) and image embeds open
+ // large on click. Not in editors, link preview cards or the YouTube card.
+ function imageClick(e: MouseEvent) {
+ if (e.button !== 0 || !(e.target instanceof HTMLImageElement)) return;
+ const img = e.target;
+ if (!img.closest('.html, .embed') || img.closest('[contenteditable], .rich, .card, .yt')) return;
+ e.preventDefault();
+ e.stopPropagation();
+ ui.image = img.currentSrc || img.src;
+ }
+
const closeOverlays = () => {
navOpen = false;
if (!panelInline) ui.panelOpen = false;
};
-
+
{#snippet rail()}
(editing = { server: null })} onedit={s => (editing = { server: s })} onbrowse={() => (ui.publicServers = true)}
@@ -134,6 +146,7 @@
{/if}
{#if share.dialog} (share.dialog = null)} />{/if}
{#if ui.prompt} { const p = ui.prompt; ui.prompt = null; p?.oncancel?.(); }} />{/if}
+
diff --git a/test/e2e/app.e2e.ts b/test/e2e/app.e2e.ts
index 1eee29b..7413c5c 100644
--- a/test/e2e/app.e2e.ts
+++ b/test/e2e/app.e2e.ts
@@ -856,6 +856,19 @@ try {
assert.ok(inline.length <= 131072, `inline image within the server limit (${inline.length})`);
console.log(`ok: inline image without upload host (${inline.length} chars)`);
+ // A click on an image in the chat shows it large; Escape or a click closes it
+ await page.locator('.chat .msg .html img').last().click();
+ const viewer = page.getByRole('dialog', { name: 'Image' });
+ await viewer.waitFor();
+ const shown = await viewer.locator('img').evaluate((i: HTMLImageElement) => [i.naturalWidth > 0, i.getBoundingClientRect().width]);
+ assert.ok(shown[0] && (shown[1] as number) > 300, `image shown large: ${shown}`);
+ await page.keyboard.press('Escape');
+ await viewer.waitFor({ state: 'detached' });
+ await page.locator('.chat .msg .html img').last().click();
+ await viewer.getByRole('button', { name: 'Close image' }).click({ position: { x: 5, y: 5 } });
+ await viewer.waitFor({ state: 'detached' });
+ console.log('ok: images open large on click');
+
// ─── Several servers at once ─────────────────────────────────────────────
const userState = (name: string) => [...bob.users.values()].find(u => u.name === name);
const until2 = (label: string, cond: () => boolean) => within(new Promise(res => {