fix: use direct favicon.ico, open links in app mode (popup window)

- Favicon loaded directly from website's /favicon.ico instead of
  Google's service (which was unreliable/blocked on internal networks)
- Letter-initial fallback when favicon can't be loaded
- External links open in popup window (app mode) instead of new tab

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas Reitz 2026-03-10 11:23:11 +01:00
parent 65c5c7b7dd
commit 0a52606012
2 changed files with 50 additions and 35 deletions

View file

@ -7,11 +7,11 @@ function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
}
/** Favicon-URL aus einer Website-URL ableiten (Google Favicon Service) */
/** Favicon-URL direkt von der Webseite laden */
function getFaviconUrl(url: string): string | null {
try {
const domain = new URL(url).hostname;
return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
const parsed = new URL(url);
return `${parsed.origin}/favicon.ico`;
} catch {
return null;
}

View file

@ -12,11 +12,11 @@ interface ExternalLink {
sortOrder: number;
}
/** Favicon-URL aus einer Website-URL ableiten (Google Favicon Service) */
/** Favicon-URL direkt von der Webseite laden */
function getFaviconUrl(url: string): string | null {
try {
const domain = new URL(url).hostname;
return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
const parsed = new URL(url);
return `${parsed.origin}/favicon.ico`;
} catch {
return null;
}
@ -82,37 +82,52 @@ export function AppLayout() {
<a
key={link.id}
href={link.url}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => {
e.preventDefault();
window.open(
link.url,
link.label,
'popup,noopener',
);
}}
className={styles.navLink}
>
{favicon ? (
<img
src={favicon}
alt=""
style={{
width: 16,
height: 16,
objectFit: 'contain',
borderRadius: 2,
}}
/>
) : (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M10 2h4v4" />
<path d="M6 10L14 2" />
<path d="M14 9v4a1 1 0 01-1 1H3a1 1 0 01-1-1V3a1 1 0 011-1h4" />
</svg>
)}
<img
src={favicon || ''}
alt=""
style={{
width: 16,
height: 16,
objectFit: 'contain',
borderRadius: 2,
}}
onError={(e) => {
// Fallback: erstes Zeichen des Labels als Text-Icon
const el = e.target as HTMLImageElement;
el.style.display = 'none';
const fallback = el.nextElementSibling;
if (fallback)
(fallback as HTMLElement).style.display =
'flex';
}}
/>
<span
style={{
display: 'none',
width: 16,
height: 16,
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.625rem',
fontWeight: 700,
background: 'rgba(255,255,255,0.15)',
borderRadius: 2,
color: 'rgba(255,255,255,0.8)',
flexShrink: 0,
}}
>
{link.label.charAt(0).toUpperCase()}
</span>
{link.label}
<svg
width="10"