Fix ArticleCard image fallback, build warning, and link safety
- Show the typographic fallback for missing images too (not only on load error), driven by component state instead of imperative class mutation — which also clears the unused-CSS-selector build warning. - Only render external links for http(s) URLs, else href=#. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,28 @@
|
|||||||
<script>
|
<script>
|
||||||
let { article, onaction, hero = false } = $props();
|
let { article, onaction, hero = false } = $props();
|
||||||
|
|
||||||
|
// Drive the image/fallback from state, not imperative class mutation — so the
|
||||||
|
// typographic fallback shows for BOTH missing and broken images, and Svelte
|
||||||
|
// sees the styles as used (no unused-selector warning).
|
||||||
|
let imgOk = $state(!!article.image_url);
|
||||||
|
|
||||||
|
// Only ever link out to real http(s) URLs (the API is clean, but cheap defense).
|
||||||
|
const safeHref =
|
||||||
|
typeof article.url === 'string' && /^https?:\/\//.test(article.url) ? article.url : '#';
|
||||||
|
|
||||||
function act(kind, value) {
|
function act(kind, value) {
|
||||||
if (value) onaction?.(kind, value);
|
if (value) onaction?.(kind, value);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<article class:hero>
|
<article class:hero>
|
||||||
<a class="media" href={article.url} target="_blank" rel="noopener">
|
<a class="media" href={safeHref} target="_blank" rel="noopener">
|
||||||
{#if article.image_url}
|
{#if article.image_url && imgOk}
|
||||||
<img src={article.image_url} alt="" loading="lazy" referrerpolicy="no-referrer"
|
<img src={article.image_url} alt="" loading="lazy" referrerpolicy="no-referrer"
|
||||||
onerror={(e) => e.currentTarget.parentElement.classList.add('noimg')} />
|
onerror={() => (imgOk = false)} />
|
||||||
|
{:else}
|
||||||
|
<span class="fallback">{article.topic ?? 'good news'}</span>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="fallback">{article.topic ?? 'good news'}</span>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="body">
|
<div class="body">
|
||||||
@@ -22,7 +32,7 @@
|
|||||||
<span class="src">{article.source}</span>
|
<span class="src">{article.source}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3><a href={article.url} target="_blank" rel="noopener">{article.title}</a></h3>
|
<h3><a href={safeHref} target="_blank" rel="noopener">{article.title}</a></h3>
|
||||||
|
|
||||||
{#if hero && article.description}
|
{#if hero && article.description}
|
||||||
<p class="desc">{article.description}</p>
|
<p class="desc">{article.description}</p>
|
||||||
@@ -59,13 +69,11 @@
|
|||||||
}
|
}
|
||||||
.media img { width: 100%; height: 100%; object-fit: cover; }
|
.media img { width: 100%; height: 100%; object-fit: cover; }
|
||||||
.fallback {
|
.fallback {
|
||||||
position: absolute; inset: 0; display: none;
|
position: absolute; inset: 0; display: flex;
|
||||||
align-items: center; justify-content: center;
|
align-items: center; justify-content: center;
|
||||||
font-family: var(--serif); font-style: italic; color: var(--sage-deep); opacity: 0.6;
|
font-family: var(--serif); font-style: italic; color: var(--sage-deep); opacity: 0.5;
|
||||||
text-transform: lowercase; letter-spacing: 0.02em;
|
text-transform: lowercase; letter-spacing: 0.02em; font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
.media.noimg img { display: none; }
|
|
||||||
.media.noimg .fallback { display: flex; }
|
|
||||||
|
|
||||||
.body { padding: 16px 18px 14px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
|
.body { padding: 16px 18px 14px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
|
||||||
.tags { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 0.74rem; }
|
.tags { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 0.74rem; }
|
||||||
|
|||||||
Reference in New Issue
Block a user