jedmund-svelte/src/lib/components/LinkCard.svelte
Justin Edmund 513b40bbc4 fix: update remaining $grey- variables in SCSS files
- Fix themes.scss to use $gray- variables
- Fix tooltip.scss to use $gray- variables
- Resolves build error with undefined variables

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-25 22:03:19 -04:00

245 lines
4.3 KiB
Svelte

<script lang="ts">
import { onMount } from 'svelte'
import type { Post } from '$lib/posts'
let {
link
}: {
link: Post['link']
} = $props()
let metadata = $state<{
url: string
title?: string
description?: string
image?: string
favicon?: string
siteName?: string
} | null>(null)
let loading = $state(true)
let error = $state(false)
const getDomain = (url: string) => {
try {
const urlObj = new URL(url)
return urlObj.hostname.replace('www.', '')
} catch {
return ''
}
}
const handleClick = () => {
const url = typeof link === 'string' ? link : link?.url
if (url) {
window.open(url, '_blank', 'noopener,noreferrer')
}
}
onMount(async () => {
// If link is just a string URL, fetch metadata
if (typeof link === 'string') {
try {
const response = await fetch(`/api/og-metadata?url=${encodeURIComponent(link)}`)
if (response.ok) {
metadata = await response.json()
} else {
error = true
}
} catch (err) {
console.error('Failed to fetch metadata:', err)
error = true
}
} else if (link) {
// Use provided metadata
metadata = link
}
loading = false
})
</script>
{#if loading}
<div class="link-card loading">
<div class="link-content">
<div class="skeleton skeleton-meta"></div>
<div class="skeleton skeleton-title"></div>
<div class="skeleton skeleton-description"></div>
</div>
</div>
{:else if error}
<button class="link-card error" onclick={handleClick}>
<div class="link-content">
<p class="error-message">Unable to load preview</p>
<p class="link-url">{typeof link === 'string' ? link : link?.url}</p>
</div>
</button>
{:else if metadata}
<button class="link-card" onclick={handleClick}>
{#if metadata.image}
<div class="link-image">
<img src={metadata.image} alt={metadata.title || 'Link preview'} />
</div>
{/if}
<div class="link-content">
<div class="link-meta">
{#if metadata.favicon}
<img src={metadata.favicon} alt="" class="favicon" />
{/if}
<span class="domain">{metadata.siteName || getDomain(metadata.url)}</span>
</div>
{#if metadata.title}
<h3 class="link-title">{metadata.title}</h3>
{/if}
{#if metadata.description}
<p class="link-description">{metadata.description}</p>
{/if}
</div>
</button>
{/if}
<style lang="scss">
.link-card {
display: flex;
flex-direction: column;
background: $gray-90;
border-radius: $image-corner-radius;
overflow: hidden;
border: 1px solid $gray-80;
padding: 0;
width: 100%;
text-align: left;
cursor: pointer;
transition: border-color 0.2s ease;
&:hover {
border-color: $gray-50;
}
&:focus {
outline: 2px solid $red-60;
outline-offset: 2px;
}
// Loading state
&.loading {
cursor: default;
&:hover {
border-color: $gray-80;
}
}
// Error state
&.error {
.link-content {
text-align: center;
}
}
}
.link-image {
width: 100%;
aspect-ratio: 2 / 1;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.link-content {
padding: $unit-3x;
display: flex;
flex-direction: column;
gap: $unit;
}
.link-meta {
display: flex;
align-items: center;
gap: $unit;
font-size: 0.875rem;
color: $gray-40;
}
.favicon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.domain {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.link-title {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
color: $gray-00;
line-height: 1.3;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.link-description {
margin: 0;
font-size: 0.875rem;
color: $gray-40;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.skeleton {
background: $gray-80;
border-radius: 4px;
animation: pulse $animation-slow ease-in-out infinite;
}
.skeleton-meta {
height: 1rem;
width: 40%;
}
.skeleton-title {
height: 1.3rem;
width: 85%;
}
.skeleton-description {
height: 3rem;
width: 100%;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.error-message {
margin: 0 0 $unit;
color: $gray-40;
font-size: 0.875rem;
}
.link-url {
margin: 0;
color: $red-60;
font-size: 0.875rem;
word-break: break-all;
}
</style>