Files
tessera-ctl/apps/web/src/components/desktop/desktop-external-links.tsx
T
schalli 5919a55cbf
Tessera CI/CD / Lint & Type Check (push) Successful in 53s
Tessera CI/CD / Tests (push) Successful in 1m33s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 19s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m13s
fix(desktop): Link-Klicks vor dem Opener-Skript des Clients abfangen
Die erste Fassung lauschte auf window und lief damit nach dem von
tauri-plugin-opener eingeschleusten Link-Skript. Dieses ruft bei
target=_blank preventDefault und plugin:opener|open_url auf, was von der
Server-Seite aus nicht freigegeben ist - der Klick verpuffte weiter
(auf VM 8233 per Klick-Protokoll gemessen). Der Helfer lauscht jetzt auf
document: nach Reacts Handlern, vor dem Opener-Skript.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:20:42 +02:00

62 lines
2.4 KiB
TypeScript

'use client';
import { useEffect } from 'react';
import { useIsDesktopClient } from '@/lib/desktop-client';
/**
* DesktopExternalLinks (quick-261001-cxo) — Links mit `target="_blank"`
* (Favoriten, „In neuem Tab öffnen“ bei XFrame/eigenen Modulen, Quellen im
* Ausschreibungs-Radar) taten im Desktop-Client beim Klick nichts, waehrend
* `window.open` (Such-Widget) ueber `on_new_window` im System-Browser landet.
*
* Ursache (VM 8233, 01.10.2026, gemessen): tauri-plugin-opener schleust ein
* Skript ein, das auf `window` solche Klicks abfaengt, `preventDefault()`
* ruft und `plugin:opener|open_url` aufruft -- von der Server-Seite aus ist
* dieser Aufruf nicht freigegeben, der Klick verpufft. Dieser Helfer kommt
* ihm zuvor und oeffnet per `window.open`; das Opener-Skript sieht den Klick
* dann als verhindert und tut nichts.
*
* Lauscht deshalb auf `document` in der Bubble-Phase: NACH Reacts Handlern
* (Wurzel `document`, frueher registriert) -- ein Klick, den die Seite selbst
* verhindert (Favoriten im Bearbeiten-Modus), bleibt verhindert --, aber VOR
* dem Opener-Skript auf `window`. Nur http/https.
*/
function externalTarget(event: MouseEvent): HTMLAnchorElement | null {
if (event.defaultPrevented) return null;
// Linksklick und Mittelklick oeffnen beide ein neues Fenster.
if (event.button !== 0 && event.button !== 1) return null;
const target = event.target;
if (!(target instanceof Element)) return null;
const anchor = target.closest('a[href]');
if (!(anchor instanceof HTMLAnchorElement)) return null;
if (anchor.target !== '_blank') return null;
if (anchor.protocol !== 'http:' && anchor.protocol !== 'https:') return null;
return anchor;
}
export function DesktopExternalLinks() {
const isDesktop = useIsDesktopClient();
useEffect(() => {
if (!isDesktop) return;
const handler = (event: MouseEvent) => {
if (event.type === 'auxclick' && event.button !== 1) return;
if (event.type === 'click' && event.button !== 0) return;
const anchor = externalTarget(event);
if (!anchor) return;
event.preventDefault();
window.open(anchor.href, '_blank', 'noopener,noreferrer');
};
document.addEventListener('click', handler);
document.addEventListener('auxclick', handler);
return () => {
document.removeEventListener('click', handler);
document.removeEventListener('auxclick', handler);
};
}, [isDesktop]);
return null;
}