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