atagen

malice

public
Star0
Watch0
1bd1ad6a629b4087d33645665833dfe68b495fd8
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: web/src/lib/LinkCard.svelte

b8ab00377c20atagen2026-10-22 05:13 UTC1
<script lang="ts">
b8ab00377c20atagen2026-10-22 05:13 UTC2
  import { messageMediaUrl } from './api';
445bf199a9cfatagen2026-10-25 02:56 UTC3
  import EmojiText from './EmojiText.svelte';
b8ab00377c20atagen2026-10-22 05:13 UTC4
  import type { LinkPreview } from './types';
b8ab00377c20atagen2026-10-22 05:13 UTC5
  import type { UrlPreview } from './urls';
b8ab00377c20atagen2026-10-22 05:13 UTC6
b8ab00377c20atagen2026-10-22 05:13 UTC7
  export let link: UrlPreview;
b8ab00377c20atagen2026-10-22 05:13 UTC8
  export let preview: LinkPreview | null = null;
b8ab00377c20atagen2026-10-22 05:13 UTC9
  export let imageUrl: (mediaId: string) => string = (mediaId) => messageMediaUrl(mediaId, 'Thumbnail');
b8ab00377c20atagen2026-10-22 05:13 UTC10
b8ab00377c20atagen2026-10-22 05:13 UTC11
  const BANNER_MIN_WIDTH = 320;
b8ab00377c20atagen2026-10-22 05:13 UTC12
  const BANNER_MIN_ASPECT = 1.3;
b8ab00377c20atagen2026-10-22 05:13 UTC13
b8ab00377c20atagen2026-10-22 05:13 UTC14
  let imageFailed = false;
b8ab00377c20atagen2026-10-22 05:13 UTC15
b8ab00377c20atagen2026-10-22 05:13 UTC16
  $: image = preview?.image && !imageFailed ? preview.image : null;
b8ab00377c20atagen2026-10-22 05:13 UTC17
  $: imageSrc = image ? imageUrl(image.media_id) : null;
b8ab00377c20atagen2026-10-22 05:13 UTC18
  $: banner =
b8ab00377c20atagen2026-10-22 05:13 UTC19
    image !== null &&
b8ab00377c20atagen2026-10-22 05:13 UTC20
    image.width !== null &&
b8ab00377c20atagen2026-10-22 05:13 UTC21
    image.height !== null &&
b8ab00377c20atagen2026-10-22 05:13 UTC22
    image.width >= BANNER_MIN_WIDTH &&
b8ab00377c20atagen2026-10-22 05:13 UTC23
    image.width / image.height >= BANNER_MIN_ASPECT;
b8ab00377c20atagen2026-10-22 05:13 UTC24
  $: rich = Boolean(preview && (preview.title || preview.description));
b8ab00377c20atagen2026-10-22 05:13 UTC25
  $: if (preview) imageFailed = false;
b8ab00377c20atagen2026-10-22 05:13 UTC26
</script>
b8ab00377c20atagen2026-10-22 05:13 UTC27
b8ab00377c20atagen2026-10-22 05:13 UTC28
<a class="link-card" class:rich class:banner href={link.href} target="_blank" rel="noopener noreferrer">
b8ab00377c20atagen2026-10-22 05:13 UTC29
  {#if rich && preview}
b8ab00377c20atagen2026-10-22 05:13 UTC30
    <span class="body">
445bf199a9cfatagen2026-10-25 02:56 UTC31
      <span class="link-host"><EmojiText text={preview.site_name ?? link.hostname} /></span>
445bf199a9cfatagen2026-10-25 02:56 UTC32
      {#if preview.title}<span class="link-title"><EmojiText text={preview.title} /></span>{/if}
445bf199a9cfatagen2026-10-25 02:56 UTC33
      {#if preview.description}<span class="link-desc"><EmojiText text={preview.description} /></span>{/if}
b8ab00377c20atagen2026-10-22 05:13 UTC34
    </span>
b8ab00377c20atagen2026-10-22 05:13 UTC35
  {:else}
b8ab00377c20atagen2026-10-22 05:13 UTC36
    <span class="body">
445bf199a9cfatagen2026-10-25 02:56 UTC37
      <span class="link-host"><EmojiText text={link.hostname} /></span>
445bf199a9cfatagen2026-10-25 02:56 UTC38
      {#if link.path}<span class="link-path"><EmojiText text={link.path} /></span>{/if}
445bf199a9cfatagen2026-10-25 02:56 UTC39
      <span class="link-url"><EmojiText text={link.display} /></span>
b8ab00377c20atagen2026-10-22 05:13 UTC40
    </span>
b8ab00377c20atagen2026-10-22 05:13 UTC41
  {/if}
b8ab00377c20atagen2026-10-22 05:13 UTC42
  {#if imageSrc}
b8ab00377c20atagen2026-10-22 05:13 UTC43
    <img class="link-image" src={imageSrc} alt="" loading="lazy" on:error={() => (imageFailed = true)} />
b8ab00377c20atagen2026-10-22 05:13 UTC44
  {/if}
b8ab00377c20atagen2026-10-22 05:13 UTC45
</a>
b8ab00377c20atagen2026-10-22 05:13 UTC46
b8ab00377c20atagen2026-10-22 05:13 UTC47
<style>
b8ab00377c20atagen2026-10-22 05:13 UTC48
  .link-card {
b8ab00377c20atagen2026-10-22 05:13 UTC49
    display: flex; gap: 10px;
05a48e9e7942atagen2026-10-24 09:32 UTC50
    border: 1px solid var(--border); border-radius: var(--r-md);
b8ab00377c20atagen2026-10-22 05:13 UTC51
    background: var(--bg-soft); padding: 8px 10px;
b8ab00377c20atagen2026-10-22 05:13 UTC52
    text-decoration: none; color: var(--text);
b8ab00377c20atagen2026-10-22 05:13 UTC53
    overflow: hidden;
b8ab00377c20atagen2026-10-22 05:13 UTC54
  }
b8ab00377c20atagen2026-10-22 05:13 UTC55
  .link-card.rich { border-left: 3px solid var(--accent); }
b8ab00377c20atagen2026-10-22 05:13 UTC56
  .link-card.banner { flex-direction: column; }
b8ab00377c20atagen2026-10-22 05:13 UTC57
  .link-card:hover { border-color: var(--accent); }
b8ab00377c20atagen2026-10-22 05:13 UTC58
  .body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
b8ab00377c20atagen2026-10-22 05:13 UTC59
  .link-host { font-size: 12.5px; font-weight: 650; }
b8ab00377c20atagen2026-10-22 05:13 UTC60
  .rich .link-host { font-size: 11.5px; font-weight: 600; color: var(--muted); }
b8ab00377c20atagen2026-10-22 05:13 UTC61
  .link-title { font-size: 14px; font-weight: 650; color: var(--accent); overflow-wrap: anywhere; }
b8ab00377c20atagen2026-10-22 05:13 UTC62
  .link-desc {
b8ab00377c20atagen2026-10-22 05:13 UTC63
    font-size: 12.5px; color: var(--muted); overflow-wrap: anywhere;
b8ab00377c20atagen2026-10-22 05:13 UTC64
    display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
b8ab00377c20atagen2026-10-22 05:13 UTC65
  }
b8ab00377c20atagen2026-10-22 05:13 UTC66
  .link-path { font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
b8ab00377c20atagen2026-10-22 05:13 UTC67
  .link-url { font-size: 11.5px; color: var(--accent); overflow-wrap: anywhere; }
05a48e9e7942atagen2026-10-24 09:32 UTC68
  .link-image { flex: none; width: 72px; height: 72px; object-fit: cover; border-radius: var(--r-sm); }
b8ab00377c20atagen2026-10-22 05:13 UTC69
  .banner .link-image { width: 100%; height: auto; max-height: 220px; }
b8ab00377c20atagen2026-10-22 05:13 UTC70
</style>