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 (