diff --git a/client/src/components/lanes/UrlPreview.tsx b/client/src/components/lanes/UrlPreview.tsx index ddae9917..5f756cc3 100644 --- a/client/src/components/lanes/UrlPreview.tsx +++ b/client/src/components/lanes/UrlPreview.tsx @@ -7,7 +7,9 @@ * `normalizePreviewUrl` refuses the dashboard's origin and anything but * http(s), and the sandbox leaves out top navigation so a page cannot move * the dashboard away. A site that forbids framing (most login pages) shows - * blank; that is the browser's rule, so the way out is the Open button. The + * blank or as the browser's broken-page icon; that is the browser's rule and + * cannot be read from here, so any page not on this machine gets a strip + * saying so with the Open button beside it. The * last address is remembered per workspace in localStorage (a viewer * convenience, never the server's). * @author Nguyễn Ngọc Trí Vĩ @@ -16,7 +18,7 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { ExternalLink, Globe, RotateCw } from "lucide-react"; -import { normalizePreviewUrl } from "../../lib/openInPreview"; +import { isLoopbackUrl, normalizePreviewUrl } from "../../lib/openInPreview"; import { isSubmitEnter } from "../../lib/keys"; import { toast } from "../../lib/toast"; @@ -80,6 +82,7 @@ export function UrlPreview({ } } + const openTab = () => url && window.open(url, "_blank", "noopener,noreferrer"); const portList = Object.entries(ports ?? {}).filter(([, p]) => Number.isInteger(p) && p > 0); const btn = "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-fg-muted transition-colors hover:bg-surface-4 hover:text-fg-primary disabled:opacity-40 disabled:hover:bg-transparent"; @@ -115,13 +118,31 @@ export function UrlPreview({ type="button" className={btn} disabled={!url} - onClick={() => url && window.open(url, "_blank", "noopener,noreferrer")} + onClick={openTab} title={t("preview.openTab")} aria-label={t("preview.openTab")} > + {url && !isLoopbackUrl(url) && ( + // An outside site: most refuse to be framed, and a refusal cannot be + // detected from here — so say it, with the way out beside it. +
+ {t("preview.blockedHint")} + +
+ )} {url ? (