diff --git a/package-lock.json b/package-lock.json index aeaea7e3..3b1b82c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1419,6 +1419,16 @@ "node": ">=10" } }, + "node_modules/@tauri-apps/api": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.9.1.tgz", + "integrity": "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, "node_modules/@tauri-apps/cli": { "version": "2.9.4", "dev": true, @@ -1462,6 +1472,15 @@ "node": ">= 10" } }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz", + "integrity": "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "dev": true, @@ -7471,6 +7490,7 @@ "@suid/icons-material": "^0.9.0", "@suid/material": "^0.19.0", "@suid/system": "^0.14.0", + "@tauri-apps/plugin-opener": "^2.5.3", "ansi-sequence-parser": "^1.1.3", "debug": "^4.4.3", "github-markdown-css": "^5.8.1", diff --git a/packages/ui/package.json b/packages/ui/package.json index 47caede3..2db33894 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -17,6 +17,7 @@ "@suid/icons-material": "^0.9.0", "@suid/material": "^0.19.0", "@suid/system": "^0.14.0", + "@tauri-apps/plugin-opener": "^2.5.3", "ansi-sequence-parser": "^1.1.3", "debug": "^4.4.3", "github-markdown-css": "^5.8.1", diff --git a/packages/ui/src/lib/notifications.tsx b/packages/ui/src/lib/notifications.tsx index 675fc884..4222c69a 100644 --- a/packages/ui/src/lib/notifications.tsx +++ b/packages/ui/src/lib/notifications.tsx @@ -1,4 +1,5 @@ import toast from "solid-toast" +import { isTauriHost } from "./runtime-env" export type ToastVariant = "info" | "success" | "warning" | "error" @@ -21,6 +22,31 @@ export type ToastPayload = { } } +async function openExternalUrl(url: string): Promise { + if (typeof window === "undefined") { + return + } + + try { + if (isTauriHost()) { + const { openUrl } = await import("@tauri-apps/plugin-opener") + await openUrl(url) + return + } + } catch (error) { + // Fall through to browser handling. + // Note: on Linux, system opener failures can throw here. + console.warn("[notifications] unable to open via system opener", error) + } + + try { + window.open(url, "_blank", "noopener,noreferrer") + } catch (error) { + console.warn("[notifications] unable to open external url", error) + toast.error("Unable to open link") + } +} + const variantAccent: Record< ToastVariant, { @@ -80,14 +106,13 @@ export function showToastNotification(payload: ToastPayload): ToastHandle { {payload.title &&

{payload.title}

}

{payload.message}

{payload.action && ( - void openExternalUrl(payload.action!.href)} > {payload.action.label} - + )}