mirror of https://github.com/vector-im/riot-web
Begin extended implementation
parent
e7f0df7fcc
commit
f32726d5ed
|
@ -32,8 +32,7 @@ import RoomTopic from "../elements/RoomTopic";
|
||||||
import RoomName from "../elements/RoomName";
|
import RoomName from "../elements/RoomName";
|
||||||
import {PlaceCallType} from "../../../CallHandler";
|
import {PlaceCallType} from "../../../CallHandler";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
import exportConversationalHistory from '../../../utils/exportUtils/exportUtils';
|
import exportConversationalHistory, { exportTypes, exportFormats } from '../../../utils/exportUtils/exportUtils';
|
||||||
import { exportFormats, exportOptions } from '../../../utils/exportUtils/exportUtils';
|
|
||||||
|
|
||||||
|
|
||||||
@replaceableComponent("views.rooms.RoomHeader")
|
@replaceableComponent("views.rooms.RoomHeader")
|
||||||
|
@ -121,7 +120,7 @@ export default class RoomHeader extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_exportConvertionalHistory = async () => {
|
_exportConvertionalHistory = async () => {
|
||||||
await exportConversationalHistory(this.props.room, exportFormats.HTML, exportOptions.TIMELINE);
|
await exportConversationalHistory(this.props.room, exportFormats.HTML, exportTypes.LAST_N_MESSAGES, 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -3,9 +3,11 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
|
import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
|
||||||
import { arrayFastClone } from "../arrays";
|
import { arrayFastClone } from "../arrays";
|
||||||
|
import { exportTypes } from "./exportUtils";
|
||||||
|
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
|
||||||
|
|
||||||
export default abstract class Exporter {
|
export default abstract class Exporter {
|
||||||
constructor(protected room: Room) {}
|
constructor(protected room: Room, protected exportType: exportTypes, protected numberOfEvents?: number) {}
|
||||||
|
|
||||||
protected getTimelineConversation = () : MatrixEvent[] => {
|
protected getTimelineConversation = () : MatrixEvent[] => {
|
||||||
if (!this.room) return;
|
if (!this.room) return;
|
||||||
|
@ -20,7 +22,7 @@ export default abstract class Exporter {
|
||||||
|
|
||||||
timelineWindow.load(null, 30);
|
timelineWindow.load(null, 30);
|
||||||
|
|
||||||
const events = timelineWindow.getEvents();
|
const events: MatrixEvent[] = timelineWindow.getEvents();
|
||||||
|
|
||||||
// Clone and reverse the events so that we preserve the order
|
// Clone and reverse the events so that we preserve the order
|
||||||
arrayFastClone(events)
|
arrayFastClone(events)
|
||||||
|
@ -32,5 +34,121 @@ export default abstract class Exporter {
|
||||||
return events;
|
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<MatrixEvent[]> => {
|
||||||
|
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<Blob>;
|
abstract export(): Promise<Blob>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,23 +22,24 @@ import BaseAvatar from "../../components/views/avatars/BaseAvatar";
|
||||||
import exportCSS from "./exportCSS";
|
import exportCSS from "./exportCSS";
|
||||||
import exportJS from "./exportJS";
|
import exportJS from "./exportJS";
|
||||||
import exportIcons from "./exportIcons";
|
import exportIcons from "./exportIcons";
|
||||||
|
import { exportTypes } from "./exportUtils";
|
||||||
|
|
||||||
export default class HTMLExporter extends Exporter {
|
export default class HTMLExporter extends Exporter {
|
||||||
protected zip: JSZip;
|
protected zip: JSZip;
|
||||||
protected avatars: Map<string, boolean>;
|
protected avatars: Map<string, boolean>;
|
||||||
protected permalinkCreator: RoomPermalinkCreator;
|
protected permalinkCreator: RoomPermalinkCreator;
|
||||||
|
|
||||||
constructor(room: Room) {
|
constructor(room: Room, exportType: exportTypes, numberOfEvents?: number) {
|
||||||
super(room);
|
super(room, exportType, numberOfEvents);
|
||||||
this.zip = new JSZip();
|
this.zip = new JSZip();
|
||||||
this.avatars = new Map<string, boolean>();
|
this.avatars = new Map<string, boolean>();
|
||||||
this.permalinkCreator = new RoomPermalinkCreator(this.room);
|
this.permalinkCreator = new RoomPermalinkCreator(this.room);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getRoomAvatar(avatarSide: number) {
|
protected async getRoomAvatar() {
|
||||||
let blob: Blob;
|
let blob: Blob;
|
||||||
const avatarUrl = Avatar.avatarUrlForRoom(this.room, avatarSide, avatarSide, "crop");
|
const avatarUrl = Avatar.avatarUrlForRoom(this.room, 32, 32, "crop");
|
||||||
const avatarPath = `room/avatar${avatarSide}.png`;
|
const avatarPath = "room.png";
|
||||||
if (avatarUrl) {
|
if (avatarUrl) {
|
||||||
const image = await fetch(avatarUrl);
|
const image = await fetch(avatarUrl);
|
||||||
blob = await image.blob();
|
blob = await image.blob();
|
||||||
|
@ -46,8 +47,8 @@ export default class HTMLExporter extends Exporter {
|
||||||
}
|
}
|
||||||
const avatar = (
|
const avatar = (
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
width={avatarSide}
|
width={32}
|
||||||
height={avatarSide}
|
height={32}
|
||||||
name={this.room.name}
|
name={this.room.name}
|
||||||
title={this.room.name}
|
title={this.room.name}
|
||||||
url={blob ? avatarPath : null}
|
url={blob ? avatarPath : null}
|
||||||
|
@ -58,7 +59,7 @@ export default class HTMLExporter extends Exporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async wrapHTML(content: string) {
|
protected async wrapHTML(content: string) {
|
||||||
const roomAvatar32 = await this.getRoomAvatar(32);
|
const roomAvatar = await this.getRoomAvatar();
|
||||||
const exportDate = formatFullDateNoDayNoTime(new Date());
|
const exportDate = formatFullDateNoDayNoTime(new Date());
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
|
const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
|
||||||
|
@ -81,7 +82,6 @@ export default class HTMLExporter extends Exporter {
|
||||||
});
|
});
|
||||||
|
|
||||||
const topicText = topic ? _t("Topic: %(topic)s", { topic }) : "";
|
const topicText = topic ? _t("Topic: %(topic)s", { topic }) : "";
|
||||||
const roomAvatar52 = await this.getRoomAvatar(52);
|
|
||||||
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
|
@ -108,7 +108,7 @@ export default class HTMLExporter extends Exporter {
|
||||||
<div class="mx_RoomHeader_wrapper" aria-owns="mx_RightPanel">
|
<div class="mx_RoomHeader_wrapper" aria-owns="mx_RightPanel">
|
||||||
<div class="mx_RoomHeader_avatar">
|
<div class="mx_RoomHeader_avatar">
|
||||||
<div class="mx_DecoratedRoomAvatar">
|
<div class="mx_DecoratedRoomAvatar">
|
||||||
${roomAvatar32}
|
${roomAvatar}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mx_RoomHeader_name">
|
<div class="mx_RoomHeader_name">
|
||||||
|
@ -143,9 +143,10 @@ export default class HTMLExporter extends Exporter {
|
||||||
role="list"
|
role="list"
|
||||||
>
|
>
|
||||||
<div class="mx_NewRoomIntro">
|
<div class="mx_NewRoomIntro">
|
||||||
${roomAvatar52}
|
${roomAvatar}
|
||||||
<h2> ${this.room.name} </h2>
|
<h2> ${this.room.name} </h2>
|
||||||
<p> ${createdText} <br/><br/> ${exportedText} </p>
|
<p> ${createdText} <br/><br/> ${exportedText} </p>
|
||||||
|
<br/>
|
||||||
<p> ${topicText} </p>
|
<p> ${topicText} </p>
|
||||||
</div>
|
</div>
|
||||||
${content}
|
${content}
|
||||||
|
|
|
@ -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";
|
import HTMLExporter from "./HtmlExport";
|
||||||
|
|
||||||
export enum exportFormats {
|
export enum exportFormats {
|
||||||
|
@ -7,14 +7,21 @@ export enum exportFormats {
|
||||||
LOGS = "LOGS",
|
LOGS = "LOGS",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum exportOptions {
|
export enum exportTypes {
|
||||||
TIMELINE = "TIMELINE",
|
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) {
|
switch (format) {
|
||||||
case exportFormats.HTML:
|
case exportFormats.HTML:
|
||||||
await new HTMLExporter(room).export();
|
await new HTMLExporter(room, exportType, numberOfEvents).export();
|
||||||
break;
|
break;
|
||||||
case exportFormats.JSON:
|
case exportFormats.JSON:
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue