diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 717a5cecb7..b05156e1ef 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -32,8 +32,7 @@ import RoomTopic from "../elements/RoomTopic"; import RoomName from "../elements/RoomName"; import {PlaceCallType} from "../../../CallHandler"; import {replaceableComponent} from "../../../utils/replaceableComponent"; -import exportConversationalHistory from '../../../utils/exportUtils/exportUtils'; -import { exportFormats, exportOptions } from '../../../utils/exportUtils/exportUtils'; +import exportConversationalHistory, { exportTypes, exportFormats } from '../../../utils/exportUtils/exportUtils'; @replaceableComponent("views.rooms.RoomHeader") @@ -121,7 +120,7 @@ export default class RoomHeader extends React.Component { } _exportConvertionalHistory = async () => { - await exportConversationalHistory(this.props.room, exportFormats.HTML, exportOptions.TIMELINE); + await exportConversationalHistory(this.props.room, exportFormats.HTML, exportTypes.LAST_N_MESSAGES, 30); } render() { diff --git a/src/utils/exportUtils/Exporter.ts b/src/utils/exportUtils/Exporter.ts index 6bdf5319f0..21b231163b 100644 --- a/src/utils/exportUtils/Exporter.ts +++ b/src/utils/exportUtils/Exporter.ts @@ -3,9 +3,11 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixClientPeg } from "../../MatrixClientPeg"; import { TimelineWindow } from "matrix-js-sdk/src/timeline-window"; import { arrayFastClone } from "../arrays"; +import { exportTypes } from "./exportUtils"; +import { RoomMember } from 'matrix-js-sdk/src/models/room-member'; export default abstract class Exporter { - constructor(protected room: Room) {} + constructor(protected room: Room, protected exportType: exportTypes, protected numberOfEvents?: number) {} protected getTimelineConversation = () : MatrixEvent[] => { if (!this.room) return; @@ -20,7 +22,7 @@ export default abstract class Exporter { timelineWindow.load(null, 30); - const events = timelineWindow.getEvents(); + const events: MatrixEvent[] = timelineWindow.getEvents(); // Clone and reverse the events so that we preserve the order arrayFastClone(events) @@ -32,5 +34,121 @@ export default abstract class Exporter { return events; }; + protected eventToJson(ev) { + const jsonEvent = ev.toJSON(); + const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent; + if (ev.isEncrypted()) { + e.curve25519Key = ev.getSenderKey(); + e.ed25519Key = ev.getClaimedEd25519Key(); + e.algorithm = ev.getWireContent().algorithm; + e.forwardingCurve25519KeyChain = ev.getForwardingCurve25519KeyChain(); + } else { + delete e.curve25519Key; + delete e.ed25519Key; + delete e.algorithm; + delete e.forwardingCurve25519KeyChain; + } + return e; + } + + + protected getRequiredEvents = async () : Promise => { + const client = MatrixClientPeg.get(); + const eventMapper = client.getEventMapper({ preventReEmit: true }); + + let prevToken: string|null = null; + let limit = this.numberOfEvents || Number.MAX_VALUE; + let events: MatrixEvent[] = []; + const stateRes: any[] = []; + while (limit) { + const eventsPerCrawl = Math.min(limit, 100); + const res = await client._createMessagesRequest(this.room.roomId, prevToken, eventsPerCrawl, "b"); + + if (res.state) stateRes.push(...res.state); + if (res.chunk.length === 0) break; + + limit -= eventsPerCrawl; + + const matrixEvents: MatrixEvent[] = res.chunk.map(eventMapper); + + matrixEvents.forEach(mxEv => events.push(mxEv)); + + prevToken = res.end; + } + events = events.reverse() + let stateEvents = []; + if (stateRes !== undefined) { + stateEvents = stateRes.map(eventMapper); + } + + const profiles = {}; + + stateEvents.forEach(ev => { + if (ev.event.content && + ev.event.content.membership === "join") { + profiles[ev.event.sender] = { + displayname: ev.event.content.displayname, + avatar_url: ev.event.content.avatar_url, + }; + } + }); + + const decryptionPromises = events + .filter(event => event.isEncrypted()) + .map(event => { + return client.decryptEventIfNeeded(event, { + isRetry: true, + emit: false, + }); + }); + + // Let us wait for all the events to get decrypted. + await Promise.all(decryptionPromises); + + const eventsWithProfile = events.map((ev) => { + const e = this.eventToJson(ev); + + let profile: any = {}; + if (e.sender in profiles) profile = profiles[e.sender]; + const object = { + event: e, + profile: profile, + }; + return object; + }); + + const matrixEvents = eventsWithProfile.map(e => { + const matrixEvent = eventMapper(e.event); + + const member = new RoomMember(this.room.roomId, matrixEvent.getSender()); + + member.name = e.profile.displayname; + + const memberEvent = eventMapper( + { + content: { + membership: "join", + avatar_url: e.profile.avatar_url, + displayname: e.profile.displayname, + }, + type: "m.room.member", + event_id: matrixEvent.getId() + ":eventIndex", + room_id: matrixEvent.getRoomId(), + sender: matrixEvent.getSender(), + origin_server_ts: matrixEvent.getTs(), + state_key: matrixEvent.getSender(), + }, + ); + + member.events.member = memberEvent; + matrixEvent.sender = member; + + return matrixEvent; + }); + + + return matrixEvents; + } + abstract export(): Promise; } diff --git a/src/utils/exportUtils/HtmlExport.tsx b/src/utils/exportUtils/HtmlExport.tsx index 371ce9fffc..069e47d15b 100644 --- a/src/utils/exportUtils/HtmlExport.tsx +++ b/src/utils/exportUtils/HtmlExport.tsx @@ -22,23 +22,24 @@ import BaseAvatar from "../../components/views/avatars/BaseAvatar"; import exportCSS from "./exportCSS"; import exportJS from "./exportJS"; import exportIcons from "./exportIcons"; +import { exportTypes } from "./exportUtils"; export default class HTMLExporter extends Exporter { protected zip: JSZip; protected avatars: Map; protected permalinkCreator: RoomPermalinkCreator; - constructor(room: Room) { - super(room); + constructor(room: Room, exportType: exportTypes, numberOfEvents?: number) { + super(room, exportType, numberOfEvents); this.zip = new JSZip(); this.avatars = new Map(); this.permalinkCreator = new RoomPermalinkCreator(this.room); } - protected async getRoomAvatar(avatarSide: number) { + protected async getRoomAvatar() { let blob: Blob; - const avatarUrl = Avatar.avatarUrlForRoom(this.room, avatarSide, avatarSide, "crop"); - const avatarPath = `room/avatar${avatarSide}.png`; + const avatarUrl = Avatar.avatarUrlForRoom(this.room, 32, 32, "crop"); + const avatarPath = "room.png"; if (avatarUrl) { const image = await fetch(avatarUrl); blob = await image.blob(); @@ -46,8 +47,8 @@ export default class HTMLExporter extends Exporter { } const avatar = (
- ${roomAvatar32} + ${roomAvatar}
@@ -143,9 +143,10 @@ export default class HTMLExporter extends Exporter { role="list" >
- ${roomAvatar52} + ${roomAvatar}

${this.room.name}

${createdText}

${exportedText}

+

${topicText}

${content} diff --git a/src/utils/exportUtils/exportUtils.ts b/src/utils/exportUtils/exportUtils.ts index 34edee0ac6..f2d85af636 100644 --- a/src/utils/exportUtils/exportUtils.ts +++ b/src/utils/exportUtils/exportUtils.ts @@ -1,4 +1,4 @@ -import { Room } from 'matrix-js-sdk/src/models/room'; +import { Room } from "matrix-js-sdk/src/models/room"; import HTMLExporter from "./HtmlExport"; export enum exportFormats { @@ -7,14 +7,21 @@ export enum exportFormats { LOGS = "LOGS", } -export enum exportOptions { +export enum exportTypes { TIMELINE = "TIMELINE", + BEGINNING = "BEGINNING", + LAST_N_MESSAGES = "LAST_N_MESSAGES", } -const exportConversationalHistory = async (room: Room, format: string, options) => { +const exportConversationalHistory = async ( + room: Room, + format: string, + exportType: exportTypes, + numberOfEvents?: number, +) => { switch (format) { case exportFormats.HTML: - await new HTMLExporter(room).export(); + await new HTMLExporter(room, exportType, numberOfEvents).export(); break; case exportFormats.JSON: break;