From 93c51ce6cc44a3f36a7f9d604c8700ecbba62899 Mon Sep 17 00:00:00 2001
From: piko-piko <142033278+piko-piko@users.noreply.github.com>
Date: Sat, 14 Mar 2026 12:20:15 +0100
Subject: [PATCH 1/2] Middle click to open the preview card thumbnail in a new
tab
---
.../components/url-preview/UrlPreviewCard.tsx | 21 ++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/app/components/url-preview/UrlPreviewCard.tsx b/src/app/components/url-preview/UrlPreviewCard.tsx
index 129614ce5..39f6fb8b1 100644
--- a/src/app/components/url-preview/UrlPreviewCard.tsx
+++ b/src/app/components/url-preview/UrlPreviewCard.tsx
@@ -14,9 +14,17 @@ import { UrlPreview, UrlPreviewContent, UrlPreviewDescription } from './UrlPrevi
import { AudioContent, ImageContent, VideoContent } from '../message';
import { Image, MediaControl, Video } from '../media';
import { ImageViewer } from '../image-viewer';
+import { downloadMedia } from '$utils/matrix';
const linkStyles = { color: color.Success.Main };
+const openMediaInNewTab = async (url: string | undefined) => {
+ if (!url) { console.warn("Attempted to open an empty url"); return}
+ const blob = await downloadMedia(url);
+ const blobUrl = URL.createObjectURL(blob);
+ window.open(blobUrl, "_blank");
+}
+
export const UrlPreviewCard = as<'div', { url: string; ts: number; mediaType?: string | null }>(
({ url, ts, mediaType, ...props }, ref) => {
const mx = useMatrixClient();
@@ -50,6 +58,17 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number; mediaType?: s
'scale',
false
);
+ const handleAuxClick = (ev: React.MouseEvent) => {
+ if (!prev['og:image']) { console.warn("No image"); return; }
+ if (ev.button === 1) {
+ ev.preventDefault();
+ const useAuthentication = true;
+ const mxcUrl = mxcUrlToHttp(mx, prev['og:image'], useAuthentication);
+ if (!mxcUrl) { console.error("Error converting mxc:// url."); return; };
+ openMediaInNewTab(mxcUrl);
+ }
+ };
+
return (
}
@@ -187,7 +207,6 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number; mediaType?: s
);
}
-
return (
Date: Sat, 14 Mar 2026 14:53:13 +0100
Subject: [PATCH 2/2] fixed formatting; fixed linting issues
---
.changeset/urlpreview-middleclick.md | 5 ++++
.../components/url-preview/UrlPreviewCard.tsx | 26 ++++++++++++-------
2 files changed, 21 insertions(+), 10 deletions(-)
create mode 100644 .changeset/urlpreview-middleclick.md
diff --git a/.changeset/urlpreview-middleclick.md b/.changeset/urlpreview-middleclick.md
new file mode 100644
index 000000000..f692e8317
--- /dev/null
+++ b/.changeset/urlpreview-middleclick.md
@@ -0,0 +1,5 @@
+---
+default: patch
+---
+
+Handles a middle-click on the url preview card thumbnail image by downloading the full image from the homeserver proxy through a fetch request and opening received blob in the new tab
diff --git a/src/app/components/url-preview/UrlPreviewCard.tsx b/src/app/components/url-preview/UrlPreviewCard.tsx
index 39f6fb8b1..5fc3ee610 100644
--- a/src/app/components/url-preview/UrlPreviewCard.tsx
+++ b/src/app/components/url-preview/UrlPreviewCard.tsx
@@ -7,23 +7,25 @@ import {
getIntersectionObserverEntry,
useIntersectionObserver,
} from '$hooks/useIntersectionObserver';
-import { mxcUrlToHttp } from '$utils/matrix';
+import { mxcUrlToHttp, downloadMedia } from '$utils/matrix';
import { useMediaAuthentication } from '$hooks/useMediaAuthentication';
import * as css from './UrlPreviewCard.css';
import { UrlPreview, UrlPreviewContent, UrlPreviewDescription } from './UrlPreview';
import { AudioContent, ImageContent, VideoContent } from '../message';
import { Image, MediaControl, Video } from '../media';
import { ImageViewer } from '../image-viewer';
-import { downloadMedia } from '$utils/matrix';
const linkStyles = { color: color.Success.Main };
const openMediaInNewTab = async (url: string | undefined) => {
- if (!url) { console.warn("Attempted to open an empty url"); return}
+ if (!url) {
+ console.warn('Attempted to open an empty url');
+ return;
+ }
const blob = await downloadMedia(url);
const blobUrl = URL.createObjectURL(blob);
- window.open(blobUrl, "_blank");
-}
+ window.open(blobUrl, '_blank');
+};
export const UrlPreviewCard = as<'div', { url: string; ts: number; mediaType?: string | null }>(
({ url, ts, mediaType, ...props }, ref) => {
@@ -59,17 +61,21 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number; mediaType?: s
false
);
const handleAuxClick = (ev: React.MouseEvent) => {
- if (!prev['og:image']) { console.warn("No image"); return; }
+ if (!prev['og:image']) {
+ console.warn('No image');
+ return;
+ }
if (ev.button === 1) {
ev.preventDefault();
- const useAuthentication = true;
- const mxcUrl = mxcUrlToHttp(mx, prev['og:image'], useAuthentication);
- if (!mxcUrl) { console.error("Error converting mxc:// url."); return; };
+ const mxcUrl = mxcUrlToHttp(mx, prev['og:image'], /* useAuthentication */ true);
+ if (!mxcUrl) {
+ console.error('Error converting mxc:// url.');
+ return;
+ }
openMediaInNewTab(mxcUrl);
}
};
-
return (