atagen

malice

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

Blame: web/src/lib/RoomThreads.svelte

7ebc2472a9e3atagen2026-10-25 09:53 UTC1
<script lang="ts">
7ebc2472a9e3atagen2026-10-25 09:53 UTC2
  import { slide } from 'svelte/transition';
7ebc2472a9e3atagen2026-10-25 09:53 UTC3
  import EmojiText from './EmojiText.svelte';
7ebc2472a9e3atagen2026-10-25 09:53 UTC4
  import PinButton from './PinButton.svelte';
7ebc2472a9e3atagen2026-10-25 09:53 UTC5
  import { formatClock, formatDay } from './format';
7ebc2472a9e3atagen2026-10-25 09:53 UTC6
  import { threadMatches } from './thread-index';
7ebc2472a9e3atagen2026-10-25 09:53 UTC7
  import type { ThreadEntry } from './thread-index';
7ebc2472a9e3atagen2026-10-25 09:53 UTC8
  import { togglePinnedThread } from './threads';
7ebc2472a9e3atagen2026-10-25 09:53 UTC9
7ebc2472a9e3atagen2026-10-25 09:53 UTC10
  export let roomId: string;
7ebc2472a9e3atagen2026-10-25 09:53 UTC11
  export let threads: ThreadEntry[];
7ebc2472a9e3atagen2026-10-25 09:53 UTC12
  export let query = '';
7ebc2472a9e3atagen2026-10-25 09:53 UTC13
  export let activeRoot: string | null = null;
7ebc2472a9e3atagen2026-10-25 09:53 UTC14
  export let onOpen: (rootEventId: string) => void;
7ebc2472a9e3atagen2026-10-25 09:53 UTC15
7ebc2472a9e3atagen2026-10-25 09:53 UTC16
  const COLLAPSED_LIMIT = 6;
7ebc2472a9e3atagen2026-10-25 09:53 UTC17
  let showAll = false;
7ebc2472a9e3atagen2026-10-25 09:53 UTC18
7ebc2472a9e3atagen2026-10-25 09:53 UTC19
  $: matching = threads.filter((t) => threadMatches(t, query));
7ebc2472a9e3atagen2026-10-25 09:53 UTC20
  $: shown = query || showAll ? matching : matching.slice(0, COLLAPSED_LIMIT);
7ebc2472a9e3atagen2026-10-25 09:53 UTC21
  $: hidden = matching.length - shown.length;
7ebc2472a9e3atagen2026-10-25 09:53 UTC22
7ebc2472a9e3atagen2026-10-25 09:53 UTC23
  function label(t: ThreadEntry): string {
7ebc2472a9e3atagen2026-10-25 09:53 UTC24
    return t.preview || (t.pinned ? 'Pinned thread' : 'Thread');
7ebc2472a9e3atagen2026-10-25 09:53 UTC25
  }
7ebc2472a9e3atagen2026-10-25 09:53 UTC26
</script>
7ebc2472a9e3atagen2026-10-25 09:53 UTC27
7ebc2472a9e3atagen2026-10-25 09:53 UTC28
<ul class="thread-fold" transition:slide={{ duration: 140 }} aria-label="Threads">
7ebc2472a9e3atagen2026-10-25 09:53 UTC29
  {#each shown as t (t.root_event_id)}
7ebc2472a9e3atagen2026-10-25 09:53 UTC30
    <li class="thread-item">
7ebc2472a9e3atagen2026-10-25 09:53 UTC31
      <button
7ebc2472a9e3atagen2026-10-25 09:53 UTC32
        type="button"
7ebc2472a9e3atagen2026-10-25 09:53 UTC33
        class="thread-row"
7ebc2472a9e3atagen2026-10-25 09:53 UTC34
        class:active={activeRoot === t.root_event_id}
7ebc2472a9e3atagen2026-10-25 09:53 UTC35
        aria-current={activeRoot === t.root_event_id ? 'true' : undefined}
7ebc2472a9e3atagen2026-10-25 09:53 UTC36
        on:click={() => onOpen(t.root_event_id)}
7ebc2472a9e3atagen2026-10-25 09:53 UTC37
      >
7ebc2472a9e3atagen2026-10-25 09:53 UTC38
        <span class="thread-preview"><EmojiText text={label(t)} /></span>
7ebc2472a9e3atagen2026-10-25 09:53 UTC39
        {#if t.reply_count > 0}<span class="thread-meta">{t.reply_count}</span>{/if}
7ebc2472a9e3atagen2026-10-25 09:53 UTC40
        {#if t.latest_ms}<time class="thread-meta" datetime={new Date(t.latest_ms).toISOString()}>{formatDay(t.latest_ms)} {formatClock(t.latest_ms)}</time>{/if}
7ebc2472a9e3atagen2026-10-25 09:53 UTC41
      </button>
7ebc2472a9e3atagen2026-10-25 09:53 UTC42
      <PinButton pinned={t.pinned} label={`thread ${label(t)}`} onToggle={() => void togglePinnedThread(roomId, t.root_event_id)} />
7ebc2472a9e3atagen2026-10-25 09:53 UTC43
    </li>
7ebc2472a9e3atagen2026-10-25 09:53 UTC44
  {/each}
7ebc2472a9e3atagen2026-10-25 09:53 UTC45
  {#if hidden > 0}
7ebc2472a9e3atagen2026-10-25 09:53 UTC46
    <li><button type="button" class="thread-more" on:click={() => (showAll = true)}>{hidden} more</button></li>
7ebc2472a9e3atagen2026-10-25 09:53 UTC47
  {/if}
7ebc2472a9e3atagen2026-10-25 09:53 UTC48
</ul>
7ebc2472a9e3atagen2026-10-25 09:53 UTC49
7ebc2472a9e3atagen2026-10-25 09:53 UTC50
<style>
7ebc2472a9e3atagen2026-10-25 09:53 UTC51
  .thread-fold { list-style: none; margin: 0 0 2px 22px; padding: 0 0 0 8px; border-left: 1px solid var(--border); display: flex; flex-direction: column; gap: 1px; }
7ebc2472a9e3atagen2026-10-25 09:53 UTC52
  .thread-item { display: flex; align-items: center; gap: 2px; }
7ebc2472a9e3atagen2026-10-25 09:53 UTC53
  .thread-row { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 6px; padding: 3px 6px; border: none; border-radius: var(--r-sm); background: transparent; color: var(--muted); font: inherit; font-size: 12.5px; text-align: left; cursor: pointer; transition: background var(--pop), color var(--pop); }
7ebc2472a9e3atagen2026-10-25 09:53 UTC54
  .thread-row:hover { background: var(--panel-2); color: var(--text); }
7ebc2472a9e3atagen2026-10-25 09:53 UTC55
  .thread-row.active { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--text); }
7ebc2472a9e3atagen2026-10-25 09:53 UTC56
  .thread-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
7ebc2472a9e3atagen2026-10-25 09:53 UTC57
  .thread-preview { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
7ebc2472a9e3atagen2026-10-25 09:53 UTC58
  .thread-meta { flex: none; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
7ebc2472a9e3atagen2026-10-25 09:53 UTC59
  .thread-item:hover :global(.pin-btn), .thread-item :global(.pin-btn.pinned) { opacity: 1; }
7ebc2472a9e3atagen2026-10-25 09:53 UTC60
  .thread-more { border: none; background: transparent; color: var(--accent); font: inherit; font-size: 12px; padding: 2px 6px; cursor: pointer; }
7ebc2472a9e3atagen2026-10-25 09:53 UTC61
</style>