fix(export): resolve edits into the original row; mark media rows
m.replace events no longer appear as their own "* text" line — the original row gets m.new_content and an (edited) marker. Media rows carry a [Image]/[Video]/[Audio]/[File] prefix and the JSON export includes mediaUrl; the UI notes attachments aren't included. Unit-tested. Fixes #50 Fixes #81 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
// 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<Record<string, string>> = {
|
||||
'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<string, string>();
|
||||
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}`;
|
||||
}
|
||||
Reference in New Issue
Block a user