f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 1 | import type { PendingMessage } from './pending'; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 2 | import type { SendState, TimelineEvent, TimelineItem } from './types'; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 3 | |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 4 | const SENDER_RUN_MS = 5 * 60 * 1000; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 5 | |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 6 | export interface Group { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 7 | key: string; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 8 | item: TimelineItem; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 9 | showSender: boolean; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 10 | } |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 11 | |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 12 | /** Consecutive events from one sender within a few minutes share a header. */ |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 13 | export function buildGroups(list: TimelineItem[]): Group[] { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 14 | const out: Group[] = []; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 15 | let prevSender: string | null = null; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 16 | let prevTs = 0; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 17 | list.forEach((item, index) => { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 18 | if (item.kind === 'event') { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 19 | const e = item.event; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 20 | const showSender = prevSender !== e.sender.user_id || Math.abs(e.timestamp_ms - prevTs) > SENDER_RUN_MS; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 21 | prevSender = e.sender.user_id; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 22 | prevTs = e.timestamp_ms; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 23 | out.push({ key: e.item_id || e.event_id || `m-${index}`, item, showSender }); |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 24 | } else { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 25 | prevSender = null; |
d07bca5da4ae | atagen | 2026-10-26 03:48 UTC | 26 | const key = |
d07bca5da4ae | atagen | 2026-10-26 03:48 UTC | 27 | item.kind === 'historyGap' ? `gap-${item.gap.id}` : |
d07bca5da4ae | atagen | 2026-10-26 03:48 UTC | 28 | item.kind === 'day' ? `day-${item.timestamp_ms}` : |
d07bca5da4ae | atagen | 2026-10-26 03:48 UTC | 29 | item.kind === 'readMarker' ? 'readMarker' : |
d07bca5da4ae | atagen | 2026-10-26 03:48 UTC | 30 | `${item.kind}-${index}`; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 31 | out.push({ key, item, showSender: true }); |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 32 | } |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 33 | }); |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 34 | return out; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 35 | } |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 36 | |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 37 | export function lastOwnSent(list: TimelineItem[]): string | null { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 38 | for (let i = list.length - 1; i >= 0; i--) { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 39 | const item = list[i]; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 40 | if (item.kind === 'event' && item.event.own && item.event.send_state.kind === 'Sent') return item.event.item_id; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 41 | } |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 42 | return null; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 43 | } |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 44 | |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 45 | export function lastSenderIs(list: TimelineItem[], userId: string | null): boolean { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 46 | for (let i = list.length - 1; i >= 0; i -= 1) { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 47 | const item = list[i]; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 48 | if (item.kind === 'event') return item.event.sender.user_id === userId; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 49 | } |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 50 | return false; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 51 | } |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 52 | |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 53 | export function pendingToEvent(p: PendingMessage, self: string | null, displayName: string | null): TimelineEvent { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 54 | const state: SendState = p.status === 'failed' ? { kind: 'Failed', will_retry: false } : { kind: 'Sending' }; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 55 | return { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 56 | item_id: `pending-${p.id}`, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 57 | event_id: null, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 58 | transaction_id: p.id, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 59 | sender: { user_id: self ?? '', display_name: displayName, avatar: null, name_ambiguous: false }, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 60 | timestamp_ms: p.createdAt, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 61 | own: true, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 62 | send_state: state, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 63 | content: { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 64 | kind: 'Message', |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 65 | message: { kind: 'Text', body: p.body, geo_uri: null, formatted_body: p.formatted, edited: false, media: null, link_previews: [] } |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 66 | }, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 67 | reply_to: p.replyTo, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 68 | thread_root: null, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 69 | thread_summary: null, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 70 | reactions: [], |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 71 | read_by: [], |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 72 | editable: false |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 73 | }; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 74 | } |