atagen

malice

public
Star0
Watch0
56f87be039353bc482f578c3a38441bd55ca13ee
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/src/lib/LinkCard.svelteSvelte70 lines3.0 KB
1
<script lang="ts">
2
  import { messageMediaUrl } from './api';
3
  import EmojiText from './EmojiText.svelte';
4
  import type { LinkPreview } from './types';
5
  import type { UrlPreview } from './urls';
6
7
  export let link: UrlPreview;
8
  export let preview: LinkPreview | null = null;
9
  export let imageUrl: (mediaId: string) => string = (mediaId) => messageMediaUrl(mediaId, 'Thumbnail');
10
11
  const BANNER_MIN_WIDTH = 320;
12
  const BANNER_MIN_ASPECT = 1.3;
13
14
  let imageFailed = false;
15
16
  $: image = preview?.image && !imageFailed ? preview.image : null;
17
  $: imageSrc = image ? imageUrl(image.media_id) : null;
18
  $: banner =
19
    image !== null &&
20
    image.width !== null &&
21
    image.height !== null &&
22
    image.width >= BANNER_MIN_WIDTH &&
23
    image.width / image.height >= BANNER_MIN_ASPECT;
24
  $: rich = Boolean(preview && (preview.title || preview.description));
25
  $: if (preview) imageFailed = false;
26
</script>
27
28
<a class="link-card" class:rich class:banner href={link.href} target="_blank" rel="noopener noreferrer">
29
  {#if rich && preview}
30
    <span class="body">
31
      <span class="link-host"><EmojiText text={preview.site_name ?? link.hostname} /></span>
32
      {#if preview.title}<span class="link-title"><EmojiText text={preview.title} /></span>{/if}
33
      {#if preview.description}<span class="link-desc"><EmojiText text={preview.description} /></span>{/if}
34
    </span>
35
  {:else}
36
    <span class="body">
37
      <span class="link-host"><EmojiText text={link.hostname} /></span>
38
      {#if link.path}<span class="link-path"><EmojiText text={link.path} /></span>{/if}
39
      <span class="link-url"><EmojiText text={link.display} /></span>
40
    </span>
41
  {/if}
42
  {#if imageSrc}
43
    <img class="link-image" src={imageSrc} alt="" loading="lazy" on:error={() => (imageFailed = true)} />
44
  {/if}
45
</a>
46
47
<style>
48
  .link-card {
49
    display: flex; gap: 10px;
50
    border: 1px solid var(--border); border-radius: var(--r-md);
51
    background: var(--bg-soft); padding: 8px 10px;
52
    text-decoration: none; color: var(--text);
53
    overflow: hidden;
54
  }
55
  .link-card.rich { border-left: 3px solid var(--accent); }
56
  .link-card.banner { flex-direction: column; }
57
  .link-card:hover { border-color: var(--accent); }
58
  .body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
59
  .link-host { font-size: 12.5px; font-weight: 650; }
60
  .rich .link-host { font-size: 11.5px; font-weight: 600; color: var(--muted); }
61
  .link-title { font-size: 14px; font-weight: 650; color: var(--accent); overflow-wrap: anywhere; }
62
  .link-desc {
63
    font-size: 12.5px; color: var(--muted); overflow-wrap: anywhere;
64
    display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
65
  }
66
  .link-path { font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
67
  .link-url { font-size: 11.5px; color: var(--accent); overflow-wrap: anywhere; }
68
  .link-image { flex: none; width: 72px; height: 72px; object-fit: cover; border-radius: var(--r-sm); }
69
  .banner .link-image { width: 100%; height: auto; max-height: 220px; }
70
</style>