// Pure helpers for ExportRoomHistory.tsx, kept SDK/DOM-free so they're easy to // unit test in isolation. export type RawExportRecord = { eventId: string; ts: number; sender: string; body: string; msgtype: string; // Set when this record IS an m.replace edit event, to the event id it targets. editsEventId?: string; // The m.new_content.body carried by an m.replace edit event. newBody?: string; }; export type ResolvedExportRecord = { eventId: string; ts: number; sender: string; body: string; msgtype: string; edited: boolean; }; // Human-readable label for media msgtypes, used to prefix txt/html export rows // so a media message isn't indistinguishable from a plain-text one. export const MEDIA_TYPE_LABELS: Partial> = { 'm.image': 'Image', 'm.video': 'Video', 'm.audio': 'Audio', 'm.file': 'File', }; /** * Resolves m.replace (edit) events against the original message they target: * the edit event is dropped from the output (it should not appear as its own * row), and the original message's body is replaced with the edit's * `m.new_content.body`, marked `edited: true`. * * Order-independent: edits may appear before or after their target in the * input (e.g. across separate back-pagination batches). */ export function resolveMessageEdits(records: RawExportRecord[]): ResolvedExportRecord[] { const edits = new Map(); for (const record of records) { if (record.editsEventId && record.newBody) { edits.set(record.editsEventId, record.newBody); } } const resolved: ResolvedExportRecord[] = []; for (const record of records) { // Edit events never become their own row. if (record.editsEventId) continue; const editedBody = edits.get(record.eventId); resolved.push({ eventId: record.eventId, ts: record.ts, sender: record.sender, body: editedBody ?? record.body, msgtype: record.msgtype, edited: editedBody !== undefined, }); } return resolved; } // Prefixes a media message's body with a "[Image]"-style marker for txt/html // export output, and appends an "(edited)" suffix when applicable. Plain-text // messages are returned unchanged (aside from the edited suffix). export function formatExportBody(record: ResolvedExportRecord): string { const label = MEDIA_TYPE_LABELS[record.msgtype]; const mediaPrefix = label ? `[${label}] ` : ''; const editedSuffix = record.edited ? ' (edited)' : ''; return `${mediaPrefix}${record.body}${editedSuffix}`; }