@@ -100,6 +102,7 @@ import { EventTimelineSet } from "matrix-js-sdk";
import axios from "axios";
import "../services/jszip.min";
import "../services/filesaver.cjs";
+import { toRaw } from "vue";
export default {
name: "RoomExport",
@@ -180,21 +183,9 @@ export default {
},
},
methods: {
- componentForEventForExport(event, forExport) {
- const comp = this.componentForEvent(event, forExport);
- const self = this;
- if (comp) {
- if (!comp.mixins) {
- comp.mixins = [];
- }
- comp.mixins.push({
- created: function() {
- console.error("Created", this.name);
- self.exportComponents.push(this);
- }
- });
- }
- return comp;
+ addComponent(item) {
+ //console.log('Add component of type', item.type.__file);
+ this.exportComponents.push(item);
},
cancelExport() {
this.cancelled = true;
@@ -242,297 +233,292 @@ export default {
}
return fetchedEvents;
},
- doExport() {
+ async doExport() {
var zip = null;
var currentMediaSize = 0;
var maxMediaSize = 1024 * 1024 * 1024; // 1GB
- this.exportComponents = [];
+ try {
+ this.exportComponents = [];
- this.getEvents()
- .then((events) => {
- var decryptionPromises = [];
- for (const event of this.events) {
- if (event.isEncrypted()) {
- decryptionPromises.push(
- this.$matrix.matrixClient.decryptEventIfNeeded(event, {
- isRetry: true,
- emit: false,
- })
- );
- }
+ const events = await this.getEvents();
+
+ let decryptionPromises = [];
+ for (const event of events) {
+ if (event.isEncrypted()) {
+ decryptionPromises.push(
+ this.$matrix.matrixClient.decryptEventIfNeeded(event, {
+ isRetry: true,
+ emit: false,
+ })
+ );
}
- return Promise.all(decryptionPromises).then(() => {
- return events;
+ }
+ await Promise.all(decryptionPromises);
+
+ // Create a timeline and add the events to that, so that relations etc are aggregated correctly!
+ this.timelineSet = new EventTimelineSet(null, { unstableClientRelationAggregation: true });
+ this.timelineSet.addEventsToTimeline(events.reverse(), true, false, this.timelineSet.getLiveTimeline(), "");
+ this.events = events;
+
+ // Need to set thread root events and replyEvents so stuff is rendered correctly.
+ this.events
+ .filter((event) => event.threadRootId && !event.parentThread)
+ .forEach((event) => {
+ const parentEvent =
+ this.timelineSet.findEventById(event.threadRootId) || this.room.findEventById(event.threadRootId);
+ if (parentEvent) {
+ parentEvent["isMxThread"] = true;
+ event["parentThread"] = parentEvent;
+ }
});
- })
- .then((events) => {
- // Create a timeline and add the events to that, so that relations etc are aggregated correctly!
- this.timelineSet = new EventTimelineSet(null, { unstableClientRelationAggregation: true });
- this.timelineSet.addEventsToTimeline(events.reverse(), true, false, this.timelineSet.getLiveTimeline(), "");
- this.events = events;
-
- // Need to set thread root events and replyEvents so stuff is rendered correctly.
- this.events
- .filter((event) => event.threadRootId && !event.parentThread)
- .forEach((event) => {
- const parentEvent =
- this.timelineSet.findEventById(event.threadRootId) || this.room.findEventById(event.threadRootId);
- if (parentEvent) {
- parentEvent["isMxThread"] = true;
- event["parentThread"] = parentEvent;
- }
- });
- this.events
- .filter((event) => event.replyEventId && !event.replyEvent)
- .forEach((event) => {
- const parentEvent =
- this.timelineSet.findEventById(event.replyEventId) || this.room.findEventById(event.replyEventId);
- if (parentEvent) {
- event["replyEvent"] = parentEvent;
- }
- });
-
- // Wait a tick so UI is updated.
- return new Promise((resolve, ignoredReject) => {
- this.$nextTick(() => {
- resolve(true);
- });
+ this.events
+ .filter((event) => event.replyEventId && !event.replyEvent)
+ .forEach((event) => {
+ const parentEvent =
+ this.timelineSet.findEventById(event.replyEventId) || this.room.findEventById(event.replyEventId);
+ if (parentEvent) {
+ event["replyEvent"] = parentEvent;
+ }
});
- })
- .then(() => {
- this.totalEvents = this.exportComponents.length;
- // UI updated, start processing events
- zip = new JSZip();
- var avatarFolder = zip.folder("avatars");
- var imageFolder = zip.folder("images");
- var audioFolder = zip.folder("audio");
- var videoFolder = zip.folder("video");
- var filesFolder = zip.folder("files");
+ // Wait a tick so UI is updated.
+ await new Promise((resolve, ignoredReject) => {
+ this.$nextTick(() => {
+ resolve(true);
+ });
+ });
- var downloadPromises = [];
+ this.totalEvents = this.exportComponents.length;
- for (const comp of this.exportComponents.filter((c) => c.event != undefined)) {
- // Avatars need downloading?
- if (comp.$el && comp.$el.nodeType == 1) {
- const avatars = comp.$el.getElementsByClassName("v-avatar");
- if (avatars && avatars.length > 0) {
- const member = this.room.getMember(comp.event.getSender());
- if (member) {
- const fileName = comp.event.getSender() + ".png";
+ let beforeExportPromises = this.exportComponents.filter((c) => c.component.exposed?.beforeExport !== undefined).map((c) => c.component.exposed.beforeExport());
+ await Promise.all(beforeExportPromises);
- const setSource = (fileName) => {
- for (let avatarIndex = 0; avatarIndex < avatars.length; avatarIndex++) {
- const avatarElement = avatars[avatarIndex];
- const images = avatarElement.getElementsByTagName("img");
- for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
- const img = images[imageIndex];
- img.onerror = undefined;
- img.removeAttribute("src");
- img.setAttribute("data-exported-src", "./avatars/" + fileName);
- }
+ // UI updated, start processing events
+ zip = new JSZip();
+ var avatarFolder = zip.folder("avatars");
+ var imageFolder = zip.folder("images");
+ var audioFolder = zip.folder("audio");
+ var videoFolder = zip.folder("video");
+ var filesFolder = zip.folder("files");
+
+ var downloadPromises = [];
+
+ for (const comp of this.exportComponents.filter((c) => c.props.originalEvent != undefined)) {
+ const event = comp.props.originalEvent;
+
+ // Avatars need downloading?
+ if (comp.el && comp.el.nodeType == 1) {
+ const avatars = comp.el.getElementsByClassName("v-avatar");
+ if (avatars && avatars.length > 0) {
+ const member = this.room.getMember(event.getSender());
+ if (member) {
+ const fileName = event.getSender() + ".png";
+
+ const setSource = (fileName) => {
+ for (let avatarIndex = 0; avatarIndex < avatars.length; avatarIndex++) {
+ const avatarElement = avatars[avatarIndex];
+ const images = avatarElement.getElementsByTagName("img");
+ for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
+ const img = images[imageIndex];
+ img.onerror = undefined;
+ img.removeAttribute("src");
+ img.setAttribute("data-exported-src", "./avatars/" + fileName);
}
- };
-
- if (!avatarFolder.file(fileName)) {
- const url = member.getAvatarUrl(
- this.$matrix.matrixClient.getHomeserverUrl(),
- 40,
- 40,
- "scale",
- true,
- false,
- this.$matrix.useAuthedMedia
- );
- if (url) {
- avatarFolder.file(fileName, "empty");
- downloadPromises.push(
- axios
- .get(url, {
- responseType: "blob",
- headers: this.$matrix.useAuthedMedia
- ? {
- Authorization: `Bearer ${this.$matrix.matrixClient.getAccessToken()}`,
- }
- : undefined,
- })
- .then((result) => {
- if (result.data) {
- avatarFolder.file(fileName, result.data);
- setSource(fileName);
- }
- })
- .catch((err) => {
- console.error("Download error: ", err);
- avatarFolder.remove(fileName);
- })
- );
- }
- } else {
- setSource(fileName);
}
+ };
+
+ if (!avatarFolder.file(fileName)) {
+ const url = member.getAvatarUrl(
+ this.$matrix.matrixClient.getHomeserverUrl(),
+ 40,
+ 40,
+ "scale",
+ true,
+ false,
+ this.$matrix.useAuthedMedia
+ );
+ if (url) {
+ avatarFolder.file(fileName, "empty");
+ downloadPromises.push(
+ axios
+ .get(url, {
+ responseType: "blob",
+ headers: this.$matrix.useAuthedMedia
+ ? {
+ Authorization: `Bearer ${this.$matrix.matrixClient.getAccessToken()}`,
+ }
+ : undefined,
+ })
+ .then((result) => {
+ if (result.data) {
+ avatarFolder.file(fileName, result.data);
+ setSource(fileName);
+ }
+ })
+ .catch((err) => {
+ console.error("Download error: ", err);
+ avatarFolder.remove(fileName);
+ })
+ );
+ }
+ } else {
+ setSource(fileName);
}
}
}
+ }
- let attachment =
- comp.event && comp.event.getId
- ? this.$matrix.attachmentManager.getEventAttachment(comp.event)
- : undefined;
- let componentClass = comp.$options
- ? comp.$options.__file.split("/").reverse()[0].split(".")[0]
- : "invalid_component";
- console.error("Processi", componentClass, comp.event, comp.originalEvent, attachment);
+ let attachment =
+ event && event.getId ? this.$matrix.attachmentManager.getEventAttachment(event) : undefined;
+ let componentClass = comp.type
+ ? comp.type.__file.split("/").reverse()[0].split(".")[0]
+ : "invalid_component";
+ if (attachment && (attachment.srcSize = 0 || currentMediaSize + attachment.srcSize <= maxMediaSize)) {
+ downloadPromises.push(
+ attachment
+ .loadBlob()
+ .then((res) => {
+ const blob = res.data;
+ if (currentMediaSize + blob.size <= maxMediaSize) {
+ currentMediaSize += blob.size;
- if (attachment && (attachment.srcSize = 0 || currentMediaSize + attachment.srcSize <= maxMediaSize)) {
- downloadPromises.push(
- attachment
- .loadSrc({ asBlob: true })
- .then((res) => {
- const blob = res.data;
- if (currentMediaSize + blob.size <= maxMediaSize) {
- currentMediaSize += blob.size;
-
- switch (componentClass) {
- case "MessageIncomingImageExport":
- case "MessageOutgoingImageExport":
- {
- let mime = blob.type;
- var extension = ".png";
- switch (mime) {
- case "image/jpeg":
- case "image/jpg":
- extension = ".jpg";
- break;
- case "image/gif":
- extension = ".gif";
- }
-
- let fileName = comp.event.getId() + extension;
- imageFolder.file(fileName, blob); // TODO calc bytes
-
- // Update source
- const images = comp.$el.getElementsByTagName("img");
- for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
- const img = images[imageIndex];
- img.removeAttribute("src");
- img.setAttribute("data-exported-src", "./images/" + fileName);
- }
- this.processedEvents += 1;
+ switch (componentClass) {
+ case "MessageIncomingImageExport":
+ case "MessageOutgoingImageExport":
+ {
+ let mime = blob.type;
+ var extension = ".png";
+ switch (mime) {
+ case "image/jpeg":
+ case "image/jpg":
+ extension = ".jpg";
+ break;
+ case "image/gif":
+ extension = ".gif";
}
- break;
- case "MessageIncomingAudioExport":
- case "MessageOutgoingAudioExport":
- {
- var extension = ".webm";
- let fileName = comp.event.getId() + extension;
- audioFolder.file(fileName, blob); // TODO calc bytes
- let elements = comp.$el.getElementsByTagName("audio");
- let element = elements && elements[0];
- if (element) {
- element.setAttribute("data-exported-src", "./audio/" + fileName);
- }
- this.processedEvents += 1;
- }
- break;
+ let fileName = event.getId() + extension;
+ imageFolder.file(fileName, blob); // TODO calc bytes
- case "MessageIncomingVideoExport":
- case "MessageOutgoingVideoExport":
- {
- var extension = ".mp4";
- let fileName = comp.event.getId() + extension;
- videoFolder.file(fileName, blob); // TODO calc bytes
- // comp.src = "./video/" + fileName;
- let elements = comp.$el.getElementsByTagName("video");
- let element = elements && elements[0];
- if (element) {
- element.setAttribute("data-exported-src", "./video/" + fileName);
- }
- this.processedEvents += 1;
+ // Update source
+ const images = comp.el.getElementsByTagName("img");
+ for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
+ const img = images[imageIndex];
+ img.removeAttribute("src");
+ img.setAttribute("data-exported-src", "./images/" + fileName);
}
- break;
+ }
+ break;
- case "MessageIncomingFileExport":
- case "MessageOutgoingFileExport":
- {
- var extension = util.getFileExtension(comp.event);
- let fileName = comp.event.getId() + extension;
- filesFolder.file(fileName, blob);
- comp.href = "./files/" + fileName;
- this.processedEvents += 1;
+ case "MessageIncomingAudioExport":
+ case "MessageOutgoingAudioExport":
+ {
+ var extension = ".webm";
+ let fileName = event.getId() + extension;
+ audioFolder.file(fileName, blob); // TODO calc bytes
+ let elements = comp.el.getElementsByTagName("audio");
+ let element = elements && elements[0];
+ if (element) {
+ element.setAttribute("data-exported-src", "./audio/" + fileName);
}
- break;
- }
- this.processedEvents += 1;
- return true;
- } else {
- this.processedEvents += 1;
- return false;
+ }
+ break;
+
+ case "MessageIncomingVideoExport":
+ case "MessageOutgoingVideoExport":
+ {
+ var extension = ".mp4";
+ let fileName = event.getId() + extension;
+ videoFolder.file(fileName, blob); // TODO calc bytes
+ // comp.src = "./video/" + fileName;
+ let elements = comp.el.getElementsByTagName("video");
+ let element = elements && elements[0];
+ if (element) {
+ element.setAttribute("data-exported-src", "./video/" + fileName);
+ }
+ }
+ break;
+
+ case "MessageIncomingFileExport":
+ case "MessageOutgoingFileExport":
+ {
+ var extension = util.getFileExtension(event);
+ let fileName = event.getId() + extension;
+ filesFolder.file(fileName, blob);
+ comp.component.data.href = "./files/" + fileName;
+ }
+ break;
}
- })
- .catch((ignoredErr) => {
this.processedEvents += 1;
- })
- );
- } else {
- this.processedEvents += 1;
+ return true;
+ } else {
+ this.processedEvents += 1;
+ return false;
+ }
+ })
+ .catch((ignoredErr) => {
+ this.processedEvents += 1;
+ })
+ );
+ } else {
+ this.processedEvents += 1;
+ }
+ }
+
+ await Promise.all(downloadPromises);
+
+ console.log("All media added, total size: " + currentMediaSize);
+
+ let root = this.$refs.exportRoot;
+
+ var doc = '\n\n
\n';
+
+ for (const sheet of document.styleSheets) {
+ doc += "\n";
+ }
+ doc +=
+ "
";
+ const getCssRules = function (el) {
+ if (el.classList.contains("op-button")) {
+ el.innerHTML = "";
+ } else {
+ for (let i = 0; i < el.children.length; i++) {
+ getCssRules(el.children[i]);
}
- doc += "\n";
}
- doc +=
- "
";
- const getCssRules = function (el) {
- if (el.classList.contains("op-button")) {
- el.innerHTML = "";
- } else {
- for (let i = 0; i < el.children.length; i++) {
- getCssRules(el.children[i]);
- }
- }
- };
- getCssRules(root);
+ };
+ getCssRules(root);
- this.$nextTick(() => {
- const contentHtml = this.$refs.exportRoot.outerHTML;
- doc += contentHtml.replaceAll("data-exported-src=", "src=");
- doc += "
";
+ this.$nextTick(() => {
+ const contentHtml = this.$refs.exportRoot.outerHTML;
+ doc += contentHtml.replaceAll("data-exported-src=", "src=");
+ doc += "