mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-24 21:16:40 +02:00
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:
parent
65c5c7b7dd
commit
0a52606012
2 changed files with 50 additions and 35 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue