Apply review suggestions

This commit is contained in:
Jaiwanth 2021-08-13 23:44:07 +05:30
parent c62210b07c
commit 7207329c15
14 changed files with 201 additions and 195 deletions

View File

@ -35,7 +35,7 @@ limitations under the License.
} }
.mx_RadioButton input[type="radio"]:checked + div > div { .mx_RadioButton input[type="radio"]:checked + div > div {
background: gray; background: $greyed-fg-color;
} }
.mx_RadioButton input[type=radio]:checked + div { .mx_RadioButton input[type=radio]:checked + div {
@ -52,8 +52,8 @@ limitations under the License.
} }
.mx_Checkbox input[type="checkbox"]:checked + label > .mx_Checkbox_background { .mx_Checkbox input[type="checkbox"]:checked + label > .mx_Checkbox_background {
background: gray; background: $greyed-fg-color;
border-color: gray; border-color: $greyed-fg-color;
} }
} }

View File

@ -312,7 +312,10 @@ function textForMessageEvent(ev: MatrixEvent): () => string | null {
message = _t('%(senderDisplayName)s sent an image.', { senderDisplayName }); message = _t('%(senderDisplayName)s sent an image.', { senderDisplayName });
} else if (ev.getType() == "m.sticker") { } else if (ev.getType() == "m.sticker") {
message = _t('%(senderDisplayName)s sent a sticker.', { senderDisplayName }); message = _t('%(senderDisplayName)s sent a sticker.', { senderDisplayName });
} else message = senderDisplayName + ': ' + message; } else {
// in this case, parse it as a plain text message
message = senderDisplayName + ': ' + message;
}
return message; return message;
}; };
} }

View File

@ -36,7 +36,6 @@ interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" |
// Whether the onClick of the avatar should be overridden to dispatch `Action.ViewUser` // Whether the onClick of the avatar should be overridden to dispatch `Action.ViewUser`
viewUserOnClick?: boolean; viewUserOnClick?: boolean;
title?: string; title?: string;
forExport?: boolean;
} }
interface IState { interface IState {
@ -90,8 +89,7 @@ export default class MemberAvatar extends React.Component<IProps, IState> {
} }
render() { render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars let { member, fallbackUserId, onClick, viewUserOnClick, ...otherProps } = this.props;
let { member, fallbackUserId, onClick, viewUserOnClick, forExport, ...otherProps } = this.props;
const userId = member ? member.userId : fallbackUserId; const userId = member ? member.userId : fallbackUserId;
if (viewUserOnClick) { if (viewUserOnClick) {

View File

@ -29,13 +29,15 @@ import {
textForFormat, textForFormat,
textForType, textForType,
} from "../../../utils/exportUtils/exportUtils"; } from "../../../utils/exportUtils/exportUtils";
import { IFieldState, IValidationResult } from "../elements/Validation"; import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import HTMLExporter from "../../../utils/exportUtils/HtmlExport"; import HTMLExporter from "../../../utils/exportUtils/HtmlExport";
import JSONExporter from "../../../utils/exportUtils/JSONExport"; import JSONExporter from "../../../utils/exportUtils/JSONExport";
import PlainTextExporter from "../../../utils/exportUtils/PlainTextExport"; import PlainTextExporter from "../../../utils/exportUtils/PlainTextExport";
import { useStateCallback } from "../../../hooks/useStateCallback"; import { useStateCallback } from "../../../hooks/useStateCallback";
import Exporter from "../../../utils/exportUtils/Exporter"; import Exporter from "../../../utils/exportUtils/Exporter";
import Spinner from "../elements/Spinner"; import Spinner from "../elements/Spinner";
import Modal from "../../../Modal";
import InfoDialog from "./InfoDialog";
interface IProps extends IDialogProps { interface IProps extends IDialogProps {
room: Room; room: Room;
@ -126,67 +128,85 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
await startExport(); await startExport();
}; };
const onValidateSize = async ({ const validateSize = withValidation({
value, rules: [
}: Pick<IFieldState, "value">): Promise<IValidationResult> => { {
const parsedSize = parseFloat(value); key: "required",
const min = 1; test({ value, allowEmpty }) {
const max = 2000; return allowEmpty || !!value;
},
invalid: () => {
const min = 1;
const max = 10 ** 8;
return _t("Enter a number between %(min)s and %(max)s", {
min,
max,
});
},
}, {
key: "number",
test: ({ value }) => {
const parsedSize = parseFloat(value);
const min = 1;
const max = 2000;
return !(isNaN(parsedSize) || min > parsedSize || parsedSize > max);
},
invalid: () => {
const min = 1;
const max = 2000;
return _t(
"Size can only be a number between %(min)s MB and %(max)s MB",
{ min, max },
);
},
},
],
});
if (isNaN(parsedSize)) { const onValidateSize = async (fieldState: IFieldState): Promise<IValidationResult> => {
return { valid: false, feedback: _t("Size must be a number") }; const result = await validateSize(fieldState);
} return result;
if (min > parsedSize || parsedSize > max) {
return {
valid: false,
feedback: _t(
"Size can only be between %(min)s MB and %(max)s MB",
{ min, max },
),
};
}
return {
valid: true,
feedback: _t("Enter size between %(min)s MB and %(max)s MB", {
min,
max,
}),
};
}; };
const onValidateNumberOfMessages = async ({ const validateNumberOfMessages = withValidation({
value, rules: [
}: Pick<IFieldState, "value">): Promise<IValidationResult> => { {
const parsedSize = parseFloat(value); key: "required",
const min = 1; test({ value, allowEmpty }) {
const max = 10 ** 8; return allowEmpty || !!value;
},
invalid: () => {
const min = 1;
const max = 10 ** 8;
return _t("Enter a number between %(min)s and %(max)s", {
min,
max,
});
},
}, {
key: "number",
test: ({ value }) => {
const parsedSize = parseFloat(value);
const min = 1;
const max = 10 ** 8;
if (isNaN(parsedSize)) return false;
return !(min > parsedSize || parsedSize > max);
},
invalid: () => {
const min = 1;
const max = 10 ** 8;
return _t(
"Number of messages can only be a number between %(min)s and %(max)s",
{ min, max },
);
},
},
],
});
if (isNaN(parsedSize)) { const onValidateNumberOfMessages = async (fieldState: IFieldState): Promise<IValidationResult> => {
return { const result = await validateNumberOfMessages(fieldState);
valid: false, return result;
feedback: _t("Number of messages must be a number"),
};
}
if (min > parsedSize || parsedSize > max) {
return {
valid: false,
feedback: _t(
"Number of messages can only be between %(min)s and %(max)s",
{ min, max },
),
};
}
return {
valid: true,
feedback: _t("Enter a number between %(min)s and %(max)s", {
min,
max,
}),
};
}; };
const onCancel = async () => { const onCancel = async () => {
@ -236,42 +256,20 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
if (exportCancelled) { if (exportCancelled) {
// Display successful cancellation message // Display successful cancellation message
return ( Modal.createTrackedDialog("Export Cancelled", "", InfoDialog, {
<BaseDialog title: _t("Export Cancelled"),
title={_t("Export Cancelled")} description: <p>{ _t("The export was cancelled successfully") }</p>,
className="mx_ExportDialog" hasCloseButton: true,
contentId="mx_Dialog_content" });
onFinished={onFinished} return null;
fixedWidth={true}
>
<p>{ _t("The export was cancelled successfully") }</p>
<DialogButtons
primaryButton={_t("Okay")}
hasCancel={false}
onPrimaryButtonClick={onFinished}
/>
</BaseDialog>
);
} else if (exportSuccessful) { } else if (exportSuccessful) {
// Display successful export message // Display successful export message
return ( Modal.createTrackedDialog("Export Successful", "", InfoDialog, {
<BaseDialog title: _t("Export Successful"),
title={_t("Export Successful")} description: <p>{ _t("Your messages were successfully exported") }</p>,
className="mx_ExportDialog" hasCloseButton: true,
contentId="mx_Dialog_content" });
onFinished={onFinished} return null;
fixedWidth={true}
>
<p>{ _t("Your messages were successfully exported") }</p>
<DialogButtons
primaryButton={_t("Okay")}
hasCancel={false}
onPrimaryButtonClick={onFinished}
/>
</BaseDialog>
);
} else if (displayCancel) { } else if (displayCancel) {
// Display cancel warning // Display cancel warning
return ( return (

View File

@ -355,10 +355,10 @@ export default class ReplyThread extends React.Component<IProps, IState> {
} else if (this.props.forExport) { } else if (this.props.forExport) {
const eventId = ReplyThread.getParentEventId(this.props.parentEv); const eventId = ReplyThread.getParentEventId(this.props.parentEv);
header = <p className="mx_ReplyThread_Export"> header = <p className="mx_ReplyThread_Export">
{ _t("In reply to <messageLink/>", { _t("In reply to <a>this message</a>",
{}, {},
{ messageLink: () => ( { a: (sub) => (
<a className="mx_reply_anchor" href={`#${eventId}`} scroll-to={eventId}> { _t("this message") } </a> <a className="mx_reply_anchor" href={`#${eventId}`} scroll-to={eventId}> { sub } </a>
), ),
}) })
} }

View File

@ -47,7 +47,7 @@ export default class CallEvent extends React.Component<IProps, IState> {
super(props); super(props);
this.state = { this.state = {
callState: this.props.callEventGrouper?.state, callState: this.props.callEventGrouper.state,
silenced: false, silenced: false,
}; };
} }
@ -210,7 +210,7 @@ export default class CallEvent extends React.Component<IProps, IState> {
render() { render() {
const event = this.props.mxEvent; const event = this.props.mxEvent;
const sender = event.sender ? event.sender.name : event.getSender(); const sender = event.sender ? event.sender.name : event.getSender();
const isVoice = this.props.callEventGrouper?.isVoice; const isVoice = this.props.callEventGrouper.isVoice;
const callType = isVoice ? _t("Voice call") : _t("Video call"); const callType = isVoice ? _t("Voice call") : _t("Video call");
const callState = this.state.callState; const callState = this.state.callState;
const hangupReason = this.props.callEventGrouper.hangupReason; const hangupReason = this.props.callEventGrouper.hangupReason;

View File

@ -201,11 +201,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
if (this.props.showGenericPlaceholder) { if (this.props.showGenericPlaceholder) {
placeholder = ( placeholder = (
<AccessibleButton className="mx_MediaBody mx_MFileBody_info" onClick={this.onPlaceholderClick}> <AccessibleButton className="mx_MediaBody mx_MFileBody_info" onClick={this.onPlaceholderClick}>
<span className="mx_MFileBody_info_icon"> <span className="mx_MFileBody_info_icon" />
{ this.props.forExport ?
<img alt="Attachment" className="mx_export_attach_icon" src="icons/attach.svg" />
: null }
</span>
<TextWithTooltip tooltip={presentableTextForFile(this.content, _t("Attachment"), true)}> <TextWithTooltip tooltip={presentableTextForFile(this.content, _t("Attachment"), true)}>
<span className="mx_MFileBody_info_filename"> <span className="mx_MFileBody_info_filename">
{ presentableTextForFile(this.content, _t("Attachment"), true, true) } { presentableTextForFile(this.content, _t("Attachment"), true, true) }

View File

@ -39,7 +39,6 @@ const RedactedBody = React.forwardRef<any, IBodyProps>(({ mxEvent, forExport },
return ( return (
<span className="mx_RedactedBody" ref={ref} title={titleText}> <span className="mx_RedactedBody" ref={ref} title={titleText}>
{ forExport ? <img alt={_t("Redacted")} className="mx_export_trash_icon" src="icons/trash.svg" /> : null }
{ text } { text }
</span> </span>
); );

View File

@ -725,6 +725,7 @@
"Invite to %(spaceName)s": "Invite to %(spaceName)s", "Invite to %(spaceName)s": "Invite to %(spaceName)s",
"Share your public space": "Share your public space", "Share your public space": "Share your public space",
"Unknown App": "Unknown App", "Unknown App": "Unknown App",
"Are you sure you want to exit during this export?": "Are you sure you want to exit during this export?",
"HTML": "HTML", "HTML": "HTML",
"JSON": "JSON", "JSON": "JSON",
"Plain Text": "Plain Text", "Plain Text": "Plain Text",
@ -1972,7 +1973,6 @@
"<reactors/><reactedWith> reacted with %(content)s</reactedWith>": "<reactors/><reactedWith> reacted with %(content)s</reactedWith>", "<reactors/><reactedWith> reacted with %(content)s</reactedWith>": "<reactors/><reactedWith> reacted with %(content)s</reactedWith>",
"<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>": "<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>", "<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>": "<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>",
"Message deleted on %(date)s": "Message deleted on %(date)s", "Message deleted on %(date)s": "Message deleted on %(date)s",
"Redacted": "Redacted",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.",
"%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s changed the room avatar to <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s changed the room avatar to <img/>",
@ -2122,8 +2122,7 @@
"QR Code": "QR Code", "QR Code": "QR Code",
"Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.", "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.",
"<a>In reply to</a> <pill>": "<a>In reply to</a> <pill>", "<a>In reply to</a> <pill>": "<a>In reply to</a> <pill>",
"In reply to <messageLink/>": "In reply to <messageLink/>", "In reply to <a>this message</a>": "In reply to <a>this message</a>",
"this message": "this message",
"Room address": "Room address", "Room address": "Room address",
"e.g. my-room": "e.g. my-room", "e.g. my-room": "e.g. my-room",
"Some characters not allowed": "Some characters not allowed", "Some characters not allowed": "Some characters not allowed",
@ -2329,16 +2328,13 @@
"There was an error updating your community. The server is unable to process your request.": "There was an error updating your community. The server is unable to process your request.", "There was an error updating your community. The server is unable to process your request.": "There was an error updating your community. The server is unable to process your request.",
"Update community": "Update community", "Update community": "Update community",
"An error has occurred.": "An error has occurred.", "An error has occurred.": "An error has occurred.",
"Size can only be between %(min)s MB and %(max)s MB": "Size can only be between %(min)s MB and %(max)s MB",
"Enter size between %(min)s MB and %(max)s MB": "Enter size between %(min)s MB and %(max)s MB",
"Number of messages must be a number": "Number of messages must be a number",
"Number of messages can only be between %(min)s and %(max)s": "Number of messages can only be between %(min)s and %(max)s",
"Enter a number between %(min)s and %(max)s": "Enter a number between %(min)s and %(max)s", "Enter a number between %(min)s and %(max)s": "Enter a number between %(min)s and %(max)s",
"Size can only be a number between %(min)s MB and %(max)s MB": "Size can only be a number between %(min)s MB and %(max)s MB",
"Number of messages can only be a number between %(min)s and %(max)s": "Number of messages can only be a number between %(min)s and %(max)s",
"Number of messages": "Number of messages", "Number of messages": "Number of messages",
"MB": "MB", "MB": "MB",
"Export Cancelled": "Export Cancelled", "Export Cancelled": "Export Cancelled",
"The export was cancelled successfully": "The export was cancelled successfully", "The export was cancelled successfully": "The export was cancelled successfully",
"Okay": "Okay",
"Export Successful": "Export Successful", "Export Successful": "Export Successful",
"Your messages were successfully exported": "Your messages were successfully exported", "Your messages were successfully exported": "Your messages were successfully exported",
"Are you sure you want to stop exporting your data? If you do, you'll need to start over.": "Are you sure you want to stop exporting your data? If you do, you'll need to start over.", "Are you sure you want to stop exporting your data? If you do, you'll need to start over.": "Are you sure you want to stop exporting your data? If you do, you'll need to start over.",

View File

@ -25,6 +25,7 @@ import { Direction, MatrixClient } from "matrix-js-sdk";
import { MutableRefObject } from "react"; import { MutableRefObject } from "react";
import JSZip from "jszip"; import JSZip from "jszip";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
import { _t } from "../../languageHandler";
type BlobFile = { type BlobFile = {
name: string; name: string;
@ -54,7 +55,7 @@ export default abstract class Exporter {
protected onBeforeUnload(e: BeforeUnloadEvent): string { protected onBeforeUnload(e: BeforeUnloadEvent): string {
e.preventDefault(); e.preventDefault();
return e.returnValue = "Are you sure you want to exit during this export?"; return e.returnValue = _t("Are you sure you want to exit during this export?");
} }
protected updateProgress(progress: string, log = true, show = true): void { protected updateProgress(progress: string, log = true, show = true): void {
@ -70,7 +71,7 @@ export default abstract class Exporter {
this.files.push(file); this.files.push(file);
} }
protected async downloadZIP(): Promise<string | null> { protected async downloadZIP(): Promise<string | void> {
const filename = `matrix-export-${formatFullDateNoDay(new Date())}.zip`; const filename = `matrix-export-${formatFullDateNoDay(new Date())}.zip`;
const zip = new JSZip(); const zip = new JSZip();

View File

@ -31,7 +31,6 @@ import EventTile, { haveTileForEvent } from "../../components/views/rooms/EventT
import DateSeparator from "../../components/views/messages/DateSeparator"; import DateSeparator from "../../components/views/messages/DateSeparator";
import BaseAvatar from "../../components/views/avatars/BaseAvatar"; import BaseAvatar from "../../components/views/avatars/BaseAvatar";
import exportJS from "!!raw-loader!./exportJS"; import exportJS from "!!raw-loader!./exportJS";
import exportIcons from "./exportIcons";
import { ExportType } from "./exportUtils"; import { ExportType } from "./exportUtils";
import { IExportOptions } from "./exportUtils"; import { IExportOptions } from "./exportUtils";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
@ -294,6 +293,7 @@ export default class HTMLExporter extends Exporter {
const mxc = mxEv.getContent().url || mxEv.getContent().file?.url; const mxc = mxEv.getContent().url || mxEv.getContent().file?.url;
eventTileMarkup = eventTileMarkup.split(mxc).join(filePath); eventTileMarkup = eventTileMarkup.split(mxc).join(filePath);
} }
eventTileMarkup = eventTileMarkup.replace(/<span class="mx_MFileBody_info_icon".*?>.*?<\/span>/, '');
if (hasAvatar) { if (hasAvatar) {
eventTileMarkup = eventTileMarkup.replace( eventTileMarkup = eventTileMarkup.replace(
encodeURI(this.getAvatarURL(mxEv)).replace(/&/g, '&amp;'), encodeURI(this.getAvatarURL(mxEv)).replace(/&/g, '&amp;'),
@ -406,10 +406,6 @@ export default class HTMLExporter extends Exporter {
this.addFile("css/style.css", new Blob([exportCSS])); this.addFile("css/style.css", new Blob([exportCSS]));
this.addFile("js/script.js", new Blob([exportJS])); this.addFile("js/script.js", new Blob([exportJS]));
for (const iconName in exportIcons) {
this.addFile(`icons/${iconName}`, new Blob([exportIcons[iconName]]));
}
await this.downloadZIP(); await this.downloadZIP();
const exportEnd = performance.now(); const exportEnd = performance.now();

View File

@ -16,33 +16,31 @@ limitations under the License.
/* eslint-disable max-len, camelcase */ /* eslint-disable max-len, camelcase */
declare const __webpack_hash__: string;
import ThemeWatcher from "../../settings/watchers/ThemeWatcher"; import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
const getExportCSS = async (): Promise<string> => { const getExportCSS = async (): Promise<string> => {
const theme = new ThemeWatcher().getEffectiveTheme(); const theme = new ThemeWatcher().getEffectiveTheme();
const hash = __webpack_hash__; const stylesheets: string[] = [];
document.querySelectorAll('link[rel="stylesheet"]').forEach((e: any) => {
const bundle = await fetch(`bundles/${hash}/bundle.css`); if (e.href.endsWith("bundle.css") || e.href.endsWith(`theme-${theme}.css`)) {
const bundleCSS = await bundle.text(); stylesheets.push(e.href);
let themeCSS: string; }
if (theme === 'light') { });
const res = await fetch(`bundles/${hash}/theme-light.css`); let CSS: string;
themeCSS = await res.text(); for (const stylesheet of stylesheets) {
} else { const res = await fetch(stylesheet);
const res = await fetch(`bundles/${hash}/theme-dark.css`); const innerText = await res.text();
themeCSS = await res.text(); CSS += innerText;
} }
const fontFaceRegex = /@font-face {.*?}/sg; const fontFaceRegex = /@font-face {.*?}/sg;
themeCSS = themeCSS.replace(fontFaceRegex, ''); CSS = CSS.replace(fontFaceRegex, '');
themeCSS = themeCSS.replace( CSS = CSS.replace(
/font-family: Inter/g, /font-family: Inter/g,
`font-family: -apple-system, BlinkMacSystemFont, avenir next, `font-family: -apple-system, BlinkMacSystemFont, avenir next,
avenir, segoe ui, helvetica neue, helvetica, Ubuntu, roboto, noto, arial, sans-serif`, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, roboto, noto, arial, sans-serif`,
); );
themeCSS = themeCSS.replace( CSS = CSS.replace(
/font-family: Inconsolata/g, /font-family: Inconsolata/g,
"font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace", "font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace",
); );
@ -149,13 +147,17 @@ const getExportCSS = async (): Promise<string> => {
left: 0; left: 0;
} }
.mx_RedactedBody {
padding-left: unset;
}
img { img {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
} }
`; `;
return themeCSS + bundleCSS + customCSS; return CSS + customCSS;
}; };
export default getExportCSS; export default getExportCSS;

View File

@ -1,23 +0,0 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import trashSVG from "!!raw-loader!../../../res/img/element-icons/trashcan.svg";
import attachSVG from "!!raw-loader!../../../res/img/element-icons/room/composer/attach.svg";
export default {
"trash.svg": trashSVG,
"attach.svg": attachSVG,
};

View File

@ -16,7 +16,7 @@ limitations under the License.
import { IContent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk"; import { IContent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk";
import { MatrixClientPeg } from "../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../src/MatrixClientPeg";
import { textForFormat, IExportOptions, ExportType } from "../../src/utils/exportUtils/exportUtils"; import { IExportOptions, ExportType, ExportFormat } from "../../src/utils/exportUtils/exportUtils";
import '../skinned-sdk'; import '../skinned-sdk';
import PlainTextExporter from "../../src/utils/exportUtils/PlainTextExport"; import PlainTextExporter from "../../src/utils/exportUtils/PlainTextExport";
import HTMLExporter from "../../src/utils/exportUtils/HtmlExport"; import HTMLExporter from "../../src/utils/exportUtils/HtmlExport";
@ -73,9 +73,60 @@ describe('export', function() {
} }
const mockRoom = createRoom(); const mockRoom = createRoom();
const ts0 = Date.now();
function mkRedactedEvent(i = 0) {
return new MatrixEvent({
type: "m.room.message",
sender: MY_USER_ID,
content: {},
unsigned: {
"age": 72,
"transaction_id": "m1212121212.23",
"redacted_because": {
"content": {},
"origin_server_ts": ts0 + i*1000,
"redacts": "$9999999999999999999999999999999999999999998",
"sender": "@me:here",
"type": "m.room.redaction",
"unsigned": {
"age": 94,
"transaction_id": "m1111111111.1",
},
"event_id": "$9999999999999999999999999999999999999999998",
"room_id": mockRoom.roomId,
},
},
event_id: "$9999999999999999999999999999999999999999999",
room_id: mockRoom.roomId,
});
}
function mkFileEvent() {
return new MatrixEvent({
"content": {
"body": "index.html",
"info": {
"mimetype": "text/html",
"size": 31613,
},
"msgtype": "m.file",
"url": "mxc://test.org",
},
"origin_server_ts": 1628872988364,
"sender": MY_USER_ID,
"type": "m.room.message",
"unsigned": {
"age": 266,
"transaction_id": "m99999999.2",
},
"event_id": "$99999999999999999999",
"room_id": mockRoom.roomId,
});
}
function mkEvents() { function mkEvents() {
const matrixEvents = []; const matrixEvents = [];
const ts0 = Date.now();
let i: number; let i: number;
// plain text // plain text
for (i = 0; i < 10; i++) { for (i = 0; i < 10; i++) {
@ -134,30 +185,7 @@ describe('export', function() {
})); }));
// redacted events // redacted events
for (i = 0; i < 10; i++) { for (i = 0; i < 10; i++) {
matrixEvents.push(new MatrixEvent({ matrixEvents.push(mkRedactedEvent(i));
type: "m.room.message",
sender: MY_USER_ID,
content: {},
unsigned: {
"age": 72,
"transaction_id": "m1212121212.23",
"redacted_because": {
"content": {},
"origin_server_ts": ts0 + i*1000,
"redacts": "$9999999999999999999999999999999999999999998",
"sender": "@me:here",
"type": "m.room.redaction",
"unsigned": {
"age": 94,
"transaction_id": "m1111111111.1",
},
"event_id": "$9999999999999999999999999999999999999999998",
"room_id": mockRoom.roomId,
},
},
event_id: "$9999999999999999999999999999999999999999999",
room_id: mockRoom.roomId,
}));
} }
return matrixEvents; return matrixEvents;
} }
@ -165,10 +193,22 @@ describe('export', function() {
const events: MatrixEvent[] = mkEvents(); const events: MatrixEvent[] = mkEvents();
it('checks if the export format is valid', function() { it('checks if the export format is valid', function() {
expect(textForFormat('HTML')).toBeTruthy(); function isValidFormat(format: string): boolean {
expect(textForFormat('JSON')).toBeTruthy(); const options: string[] = Object.values(ExportFormat);
expect(textForFormat('PLAIN_TEXT')).toBeTruthy(); return options.includes(format);
expect(() => textForFormat('PDF')).toThrowError("Unknown format"); }
expect(isValidFormat("Html")).toBeTruthy();
expect(isValidFormat("Json")).toBeTruthy();
expect(isValidFormat("PlainText")).toBeTruthy();
expect(isValidFormat("Pdf")).toBeFalsy();
});
it("checks if the icons' html corresponds to export regex", function() {
const exporter = new HTMLExporter(mockRoom, ExportType.Beginning, mockExportOptions, null);
const fileRegex = /<span class="mx_MFileBody_info_icon">.*?<\/span>/;
expect(fileRegex.test(
renderToString(exporter.getEventTile(mkFileEvent(), true))),
).toBeTruthy();
}); });
it('checks if the export options are valid', function() { it('checks if the export options are valid', function() {