diff --git a/src/DateUtils.ts b/src/DateUtils.ts index e4a1175d88..b567337e2c 100644 --- a/src/DateUtils.ts +++ b/src/DateUtils.ts @@ -136,3 +136,19 @@ export function wantsDateSeparator(prevEventDate: Date, nextEventDate: Date): bo // Compare weekdays return prevEventDate.getDay() !== nextEventDate.getDay(); } + + +export function formatFullDateNoDayNoTime(date: Date) { + const dateTime = date.getFullYear() + + "-" + + pad(date.getMonth()) + + "-" + + pad(date.getDate()) + + _t(" at ") + + pad(date.getHours()) + + "." + + pad(date.getMinutes()) + + "." + + pad(date.getSeconds()); + return dateTime; +} diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index 8550f13e2d..67d6ef38f0 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -16,14 +16,15 @@ limitations under the License. */ import React from 'react'; -import {RoomMember} from "matrix-js-sdk/src/models/room-member"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import dis from "../../../dispatcher/dispatcher"; -import {Action} from "../../../dispatcher/actions"; +import { Action } from "../../../dispatcher/actions"; import BaseAvatar from "./BaseAvatar"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; -import {mediaFromMxc} from "../../../customisations/Media"; -import {ResizeMethod} from "../../../Avatar"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { mediaFromMxc } from "../../../customisations/Media"; +import { ResizeMethod } from "../../../Avatar"; +import { omit } from "lodash"; interface IProps extends Omit, "name" | "idName" | "url"> { member: RoomMember; @@ -94,6 +95,8 @@ export default class MemberAvatar extends React.Component { let {member, fallbackUserId, onClick, viewUserOnClick, ...otherProps} = this.props; const userId = member ? member.userId : fallbackUserId; + otherProps = omit(otherProps, "avatarSrc"); + if (viewUserOnClick) { onClick = () => { dis.dispatch({ diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index df1037a863..682a31e0a3 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -103,6 +103,7 @@ "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", + " at ": " at ", "Who would you like to add to this community?": "Who would you like to add to this community?", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID", "Invite new community members": "Invite new community members", diff --git a/src/utils/exportUtils/HtmlExport.tsx b/src/utils/exportUtils/HtmlExport.tsx index 80ba643f81..eeb9f6a684 100644 --- a/src/utils/exportUtils/HtmlExport.tsx +++ b/src/utils/exportUtils/HtmlExport.tsx @@ -9,7 +9,7 @@ import { Exporter } from "./Exporter"; import { renderToStaticMarkup } from 'react-dom/server' import { Layout } from "../../settings/Layout"; import { shouldFormContinuation } from "../../components/structures/MessagePanel"; -import { wantsDateSeparator } from "../../DateUtils"; +import { formatFullDateNoDayNoTime, wantsDateSeparator } from "../../DateUtils"; import { RoomPermalinkCreator } from "../permalinks/Permalinks"; import * as ponyfill from "web-streams-polyfill/ponyfill" import * as Avatar from "../../Avatar"; @@ -18,6 +18,7 @@ import DateSeparator from "../../components/views/messages/DateSeparator"; import exportCSS from "./exportCSS"; import exportJS from "./exportJS"; import BaseAvatar from "../../components/views/avatars/BaseAvatar"; +import exportIcons from "./exportIcons"; export default class HTMLExporter extends Exporter { protected zip: JSZip; @@ -179,6 +180,37 @@ export default class HTMLExporter extends Exporter { return wantsDateSeparator(prevEvent.getDate(), event.getDate()); } + protected splitFileName(file: string) { + const lastDot = file.lastIndexOf('.'); + if (lastDot === -1) return [file, ""]; + const fileName = file.slice(0, lastDot); + const ext = file.slice(lastDot + 1); + return [fileName, ext]; + } + + protected getFilePath(event: MatrixEvent) { + const mediaType = event.getContent().msgtype; + let fileDirectory: string; + switch (mediaType) { + case "m.image": + fileDirectory = "images"; + break; + case "m.video": + fileDirectory = "videos"; + break; + case "m.audio": + fileDirectory = "audio"; + break; + default: + fileDirectory = "files"; + break; + } + const fileDate = formatFullDateNoDayNoTime(new Date(event.getTs())); + const [fileName, fileExt] = this.splitFileName(event.getContent().body); + const filePath = fileDirectory + "/" + fileName + '-' + fileDate + '.' + fileExt; + return filePath; + } + protected getEventTile(mxEv: MatrixEvent, continuation: boolean, mediaSrc?: string) { const hasAvatar = this.hasAvatar(mxEv); @@ -215,31 +247,12 @@ export default class HTMLExporter extends Exporter { protected async createMessageBody(mxEv: MatrixEvent, joined = false) { let eventTile: JSX.Element; switch (mxEv.getContent().msgtype) { - case "m.image": { - const blob = await this.getMediaBlob(mxEv); - const filePath = `images/${mxEv.getId()}.${blob.type.replace("image/", "")}`; - eventTile = this.getEventTile(mxEv, joined, filePath); - this.zip.file(filePath, blob); - break; - } - case "m.video": { - const blob = await this.getMediaBlob(mxEv); - const filePath = `videos/${mxEv.getId()}.${blob.type.replace("video/", "")}`; - eventTile = this.getEventTile(mxEv, joined, filePath); - this.zip.file(filePath, blob); - break; - } + case "m.image": + case "m.file": + case "m.video": case "m.audio": { const blob = await this.getMediaBlob(mxEv); - const filePath = `audio/${mxEv.getId()}.${blob.type.replace("audio/", "")}`; - eventTile = this.getEventTile(mxEv, joined, filePath); - this.zip.file(filePath, blob); - break; - } - case "m.file": { - const blob = await this.getMediaBlob(mxEv); - const fileName = mxEv.getContent().body; - const filePath = `files/${fileName}`; + const filePath = this.getFilePath(mxEv); eventTile = this.getEventTile(mxEv, joined, filePath); this.zip.file(filePath, blob); break; @@ -273,8 +286,10 @@ export default class HTMLExporter extends Exporter { this.zip.file("index.html", html); this.zip.file("css/style.css", exportCSS); this.zip.file("js/script.js", exportJS); - - const filename = `matrix-export-${new Date().toISOString()}.zip`; + for (const iconName in exportIcons) { + this.zip.file(`icons/${iconName}`, exportIcons[iconName]); + } + const filename = `matrix-export-${formatFullDateNoDayNoTime(new Date())}.zip`; //Generate the zip file asynchronously const blob = await this.zip.generateAsync({ type: "blob" });