atagen

malice

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

Blame: web/src/lib/thread-index.ts

7ebc2472a9e3atagen2026-10-25 09:53 UTC1
import type { TimelineEvent, TimelineItem } from './types';
7ebc2472a9e3atagen2026-10-25 09:53 UTC2
7ebc2472a9e3atagen2026-10-25 09:53 UTC3
export interface ThreadEntry {
7ebc2472a9e3atagen2026-10-25 09:53 UTC4
  room_id: string;
7ebc2472a9e3atagen2026-10-25 09:53 UTC5
  root_event_id: string;
7ebc2472a9e3atagen2026-10-25 09:53 UTC6
  preview: string;
7ebc2472a9e3atagen2026-10-25 09:53 UTC7
  sender: string;
7ebc2472a9e3atagen2026-10-25 09:53 UTC8
  reply_count: number;
7ebc2472a9e3atagen2026-10-25 09:53 UTC9
  latest_ms: number;
7ebc2472a9e3atagen2026-10-25 09:53 UTC10
  pinned: boolean;
7ebc2472a9e3atagen2026-10-25 09:53 UTC11
}
7ebc2472a9e3atagen2026-10-25 09:53 UTC12
7ebc2472a9e3atagen2026-10-25 09:53 UTC13
function previewOf(event: TimelineEvent): string {
7ebc2472a9e3atagen2026-10-25 09:53 UTC14
  const content = event.content;
7ebc2472a9e3atagen2026-10-25 09:53 UTC15
  if (content.kind === 'Message') return content.message.media?.caption ?? content.message.body;
7ebc2472a9e3atagen2026-10-25 09:53 UTC16
  if (content.kind === 'Sticker') return content.body || 'Sticker';
7ebc2472a9e3atagen2026-10-25 09:53 UTC17
  if (content.kind === 'Poll') return content.question;
7ebc2472a9e3atagen2026-10-25 09:53 UTC18
  return '';
7ebc2472a9e3atagen2026-10-25 09:53 UTC19
}
7ebc2472a9e3atagen2026-10-25 09:53 UTC20
7ebc2472a9e3atagen2026-10-25 09:53 UTC21
function entryFor(roomId: string, event: TimelineEvent, pinned: boolean): ThreadEntry | null {
7ebc2472a9e3atagen2026-10-25 09:53 UTC22
  if (!event.event_id || event.thread_root) return null;
7ebc2472a9e3atagen2026-10-25 09:53 UTC23
  return {
7ebc2472a9e3atagen2026-10-25 09:53 UTC24
    room_id: roomId,
7ebc2472a9e3atagen2026-10-25 09:53 UTC25
    root_event_id: event.event_id,
7ebc2472a9e3atagen2026-10-25 09:53 UTC26
    preview: previewOf(event),
7ebc2472a9e3atagen2026-10-25 09:53 UTC27
    sender: event.sender.display_name ?? event.sender.user_id,
7ebc2472a9e3atagen2026-10-25 09:53 UTC28
    reply_count: event.thread_summary?.reply_count ?? 0,
7ebc2472a9e3atagen2026-10-25 09:53 UTC29
    latest_ms: event.thread_summary?.latest_event_timestamp_ms ?? event.timestamp_ms,
7ebc2472a9e3atagen2026-10-25 09:53 UTC30
    pinned
7ebc2472a9e3atagen2026-10-25 09:53 UTC31
  };
7ebc2472a9e3atagen2026-10-25 09:53 UTC32
}
7ebc2472a9e3atagen2026-10-25 09:53 UTC33
7ebc2472a9e3atagen2026-10-25 09:53 UTC34
/** Pinned threads first, then by latest activity. Pins whose root isn't loaded still get a row. */
7ebc2472a9e3atagen2026-10-25 09:53 UTC35
export function roomThreads(roomId: string, items: TimelineItem[], pinnedRoots: string[]): ThreadEntry[] {
7ebc2472a9e3atagen2026-10-25 09:53 UTC36
  const pinned = new Set(pinnedRoots);
7ebc2472a9e3atagen2026-10-25 09:53 UTC37
  const found = new Map<string, ThreadEntry>();
7ebc2472a9e3atagen2026-10-25 09:53 UTC38
  for (const item of items) {
7ebc2472a9e3atagen2026-10-25 09:53 UTC39
    if (item.kind !== 'event') continue;
7ebc2472a9e3atagen2026-10-25 09:53 UTC40
    const id = item.event.event_id;
7ebc2472a9e3atagen2026-10-25 09:53 UTC41
    if (!id || (!pinned.has(id) && (item.event.thread_summary?.reply_count ?? 0) === 0)) continue;
7ebc2472a9e3atagen2026-10-25 09:53 UTC42
    const entry = entryFor(roomId, item.event, pinned.has(id));
7ebc2472a9e3atagen2026-10-25 09:53 UTC43
    if (entry) found.set(id, entry);
7ebc2472a9e3atagen2026-10-25 09:53 UTC44
  }
7ebc2472a9e3atagen2026-10-25 09:53 UTC45
  for (const root of pinnedRoots) {
7ebc2472a9e3atagen2026-10-25 09:53 UTC46
    if (!found.has(root)) {
7ebc2472a9e3atagen2026-10-25 09:53 UTC47
      found.set(root, { room_id: roomId, root_event_id: root, preview: '', sender: '', reply_count: 0, latest_ms: 0, pinned: true });
7ebc2472a9e3atagen2026-10-25 09:53 UTC48
    }
7ebc2472a9e3atagen2026-10-25 09:53 UTC49
  }
7ebc2472a9e3atagen2026-10-25 09:53 UTC50
  return [...found.values()].sort((a, b) => Number(b.pinned) - Number(a.pinned) || b.latest_ms - a.latest_ms);
7ebc2472a9e3atagen2026-10-25 09:53 UTC51
}
7ebc2472a9e3atagen2026-10-25 09:53 UTC52
7ebc2472a9e3atagen2026-10-25 09:53 UTC53
export function threadMatches(entry: ThreadEntry, query: string): boolean {
7ebc2472a9e3atagen2026-10-25 09:53 UTC54
  if (!query) return true;
7ebc2472a9e3atagen2026-10-25 09:53 UTC55
  return entry.preview.toLowerCase().includes(query) || entry.sender.toLowerCase().includes(query);
7ebc2472a9e3atagen2026-10-25 09:53 UTC56
}