fix(web): Favoriten-Platzhalterbuchstabe verschwindet, sobald das Logo geladen ist
Bei Logos mit durchsichtigem Hintergrund schien der Buchstabe durch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T
|
|||||||
|
|
||||||
### Behoben
|
### Behoben
|
||||||
|
|
||||||
|
- Favoriten: Bei Logos mit durchsichtigem Hintergrund schien der graue Platzhalter-Buchstabe durch. Er verschwindet jetzt, sobald das Logo geladen ist.
|
||||||
- Linux-App: Links, die in einem neuen Fenster aufgehen (zum Beispiel aus Notizen oder Favoriten), übergibt die App jetzt an das Öffnen-Programm Ihres Systems statt an eine mitgebrachte Kopie. Auf manchen Systemen öffnete sich dadurch vorher kein Browser.
|
- Linux-App: Links, die in einem neuen Fenster aufgehen (zum Beispiel aus Notizen oder Favoriten), übergibt die App jetzt an das Öffnen-Programm Ihres Systems statt an eine mitgebrachte Kopie. Auf manchen Systemen öffnete sich dadurch vorher kein Browser.
|
||||||
|
|
||||||
## 1.10.0 – 2026-10-06
|
## 1.10.0 – 2026-10-06
|
||||||
|
|||||||
@@ -414,6 +414,25 @@ describe('FavoritesWidget', () => {
|
|||||||
expect(screen.getByTestId('letter-fallback-fav-id-1')).toHaveTextContent('G');
|
expect(screen.getByTestId('letter-fallback-fav-id-1')).toHaveTextContent('G');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('06.10.: geladenes Logo blendet den Buchstaben aus (durchsichtige Logos), Fehler zeigt ihn wieder', async () => {
|
||||||
|
render(<FavoritesWidget instanceId="fav-1" config={{}} isEditMode={false} />);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('GitHub')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
const proxyImg = screen.getByTestId('icon-proxy-fav-id-1') as HTMLImageElement;
|
||||||
|
expect(screen.getByTestId('letter-fallback-fav-id-1')).toBeInTheDocument();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.load(proxyImg);
|
||||||
|
});
|
||||||
|
expect(screen.queryByTestId('letter-fallback-fav-id-1')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.error(proxyImg);
|
||||||
|
});
|
||||||
|
expect(screen.getByTestId('letter-fallback-fav-id-1')).toHaveTextContent('G');
|
||||||
|
});
|
||||||
|
|
||||||
it('ausdrueckliche Symbol-Adresse (260929-lh3): Proxy -> Browser laedt iconUrl direkt (no-referrer) -> Origin-Favicon -> Buchstabe', async () => {
|
it('ausdrueckliche Symbol-Adresse (260929-lh3): Proxy -> Browser laedt iconUrl direkt (no-referrer) -> Origin-Favicon -> Buchstabe', async () => {
|
||||||
mockFetch.mockResolvedValue([
|
mockFetch.mockResolvedValue([
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -543,7 +543,9 @@ function getDirectCandidates(iconUrl: string | null, url: string): string[] {
|
|||||||
* Browser aber das Bild), dann `{origin}/favicon.ico` (entfaellt, wenn
|
* Browser aber das Bild), dann `{origin}/favicon.ico` (entfaellt, wenn
|
||||||
* identisch). Erreicht auch interne Hosts, die der SSRF-Schutz des
|
* identisch). Erreicht auch interne Hosts, die der SSRF-Schutz des
|
||||||
* Servers absichtlich ablehnt.
|
* Servers absichtlich ablehnt.
|
||||||
* 3. `none` — der Buchstaben-Platzhalter liegt IMMER darunter.
|
* 3. `none` — der Buchstaben-Platzhalter steht da, solange kein Bild
|
||||||
|
* geladen ist. Sobald ein Bild geladen ist, verschwindet er: bei Logos mit
|
||||||
|
* durchsichtigem Hintergrund schien er sonst durch (06.10.2026).
|
||||||
*
|
*
|
||||||
* Bewusst KEIN Drittanbieter-Favicon-Dienst: der wuerde Hostnamen nach
|
* Bewusst KEIN Drittanbieter-Favicon-Dienst: der wuerde Hostnamen nach
|
||||||
* aussen geben und interne Hosts ohnehin nicht kennen.
|
* aussen geben und interne Hosts ohnehin nicht kennen.
|
||||||
@@ -590,15 +592,18 @@ function FavoriteIcon({
|
|||||||
// 'proxy' | Index in directSrcs | Ende der Kette (Buchstabe).
|
// 'proxy' | Index in directSrcs | Ende der Kette (Buchstabe).
|
||||||
const [stage, setStage] = useState<'proxy' | number>(proxySrc ? 'proxy' : 0);
|
const [stage, setStage] = useState<'proxy' | number>(proxySrc ? 'proxy' : 0);
|
||||||
const directSrc = typeof stage === 'number' ? (directSrcs[stage] ?? null) : null;
|
const directSrc = typeof stage === 'number' ? (directSrcs[stage] ?? null) : null;
|
||||||
|
const [imageLoaded, setImageLoaded] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`relative flex shrink-0 items-center justify-center bg-muted ${box}`}>
|
<div className={`relative flex shrink-0 items-center justify-center bg-muted ${box}`}>
|
||||||
|
{!imageLoaded && (
|
||||||
<span
|
<span
|
||||||
data-testid={`letter-fallback-${fav.id}`}
|
data-testid={`letter-fallback-${fav.id}`}
|
||||||
className={`absolute inset-0 flex items-center justify-center font-semibold text-muted-foreground ${large ? 'text-base' : 'text-xs'}`}
|
className={`absolute inset-0 flex items-center justify-center font-semibold text-muted-foreground ${large ? 'text-base' : 'text-xs'}`}
|
||||||
>
|
>
|
||||||
{getFallbackLetter(fav.title)}
|
{getFallbackLetter(fav.title)}
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
{stage === 'proxy' && proxySrc && (
|
{stage === 'proxy' && proxySrc && (
|
||||||
<img
|
<img
|
||||||
data-testid={`icon-proxy-${fav.id}`}
|
data-testid={`icon-proxy-${fav.id}`}
|
||||||
@@ -609,7 +614,11 @@ function FavoriteIcon({
|
|||||||
height={px}
|
height={px}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className={`relative rounded-sm ${img}`}
|
className={`relative rounded-sm ${img}`}
|
||||||
onError={() => setStage(0)}
|
onLoad={() => setImageLoaded(true)}
|
||||||
|
onError={() => {
|
||||||
|
setImageLoaded(false);
|
||||||
|
setStage(0);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{directSrc && (
|
{directSrc && (
|
||||||
@@ -623,7 +632,11 @@ function FavoriteIcon({
|
|||||||
loading="lazy"
|
loading="lazy"
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
className={`relative rounded-sm ${img}`}
|
className={`relative rounded-sm ${img}`}
|
||||||
onError={() => setStage((prev) => (typeof prev === 'number' ? prev + 1 : 0))}
|
onLoad={() => setImageLoaded(true)}
|
||||||
|
onError={() => {
|
||||||
|
setImageLoaded(false);
|
||||||
|
setStage((prev) => (typeof prev === 'number' ? prev + 1 : 0));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user