Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include a file-safe room name and ISO date in chat exports #9440

Merged
merged 29 commits into from Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
55c48a5
conversation export named after room
Sinharitik589 Mar 4, 2022
04d7e62
sanitization added for exported file name
Sinharitik589 Mar 8, 2022
65bdb67
sanitization added for exported file name
Sinharitik589 Mar 8, 2022
cb4a957
sanitization added for exported file name
Sinharitik589 Mar 8, 2022
439c4ca
sanitization added for exported file name=>lint error fixed
Sinharitik589 Mar 8, 2022
9958015
sanitization added for exported file name=>lint error fixed
Sinharitik589 Mar 9, 2022
f2ceb5d
sanitization added for exported file name=>redundancy removed
Sinharitik589 Mar 9, 2022
e75b306
sanitization added for exported file name=>redundancy removed
Sinharitik589 Mar 12, 2022
572cf47
reverted to previous commit
Sinharitik589 Mar 12, 2022
b0dac2f
sanitization added for exported file name=>redundancy removed
Sinharitik589 Mar 15, 2022
e73fa9b
Merge branch 'develop' into issue_19724
Sinharitik589 Mar 15, 2022
6df5cbd
Merge branch 'develop' into issue_19724
t3chguy May 4, 2022
245c3bd
exported chat date iso formatted
yaya-usman May 10, 2022
563c651
Merge branch 'matrix-org:develop' into issue_19724
Sinharitik589 May 16, 2022
f086670
conversation export named after room
Sinharitik589 May 16, 2022
82086ec
Merge branch 'matrix-org:develop' into issue_19724
Sinharitik589 May 17, 2022
26854cd
Merge branch 'develop' into issue_19724
t3chguy May 18, 2022
677e9e7
conversation export named after room
Sinharitik589 May 18, 2022
7974c3b
Merge branch 'develop' into issue_19724
t3chguy May 20, 2022
eb9f262
code refacto filename date format
yaya-usman May 10, 2022
58b7b4a
Merge branch 'develop' of https://github.com/yaya-usman/matrix-react-…
yaya-usman Aug 6, 2022
3b5f57a
Merge remote-tracking branch 'Sinharitik589/issue_19724' into travis/…
turt2live Oct 17, 2022
40cf993
Merge remote-tracking branch 'yaya-usman/ace-branch' into travis/room…
turt2live Oct 17, 2022
278558f
Add docs to fn
turt2live Oct 17, 2022
b1a6abe
Bring in a util library for sanitizing
turt2live Oct 17, 2022
a4e492e
Extract file naming function and make consistent for all 3 types
turt2live Oct 17, 2022
1cdd931
Write tests & associated fixes
turt2live Oct 17, 2022
d07b72a
Apply linters locally
turt2live Oct 17, 2022
9226ab1
Include new date util in index
turt2live Oct 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -111,6 +111,7 @@
"react-focus-lock": "^2.5.1",
"react-transition-group": "^4.4.1",
"rfc4648": "^1.4.0",
"sanitize-filename": "^1.6.3",
"sanitize-html": "^2.3.2",
"tar-js": "^0.3.0",
"ua-parser-js": "^1.0.2",
Expand Down
11 changes: 11 additions & 0 deletions src/DateUtils.ts
@@ -1,6 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2022 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.
Expand Down Expand Up @@ -177,6 +178,16 @@ export function formatFullDateNoDay(date: Date) {
});
}

/**
* Returns an ISO date string without textual description of the date (ie: no "Wednesday" or
* similar)
* @param date The date to format.
* @returns The date string in ISO format.
*/
export function formatFullDateNoDayISO(date: Date): string {
return date.toISOString();
}

export function formatFullDateNoDayNoTime(date: Date) {
return (
date.getFullYear() +
Expand Down
2 changes: 1 addition & 1 deletion src/i18n/strings/en_EN.json
Expand Up @@ -756,6 +756,7 @@
"Zoom in": "Zoom in",
"Zoom out": "Zoom out",
"Are you sure you want to exit during this export?": "Are you sure you want to exit during this export?",
"Unnamed Room": "Unnamed Room",
"Generating a ZIP": "Generating a ZIP",
"Fetched %(count)s events out of %(total)s|other": "Fetched %(count)s events out of %(total)s",
"Fetched %(count)s events out of %(total)s|one": "Fetched %(count)s event out of %(total)s",
Expand Down Expand Up @@ -2768,7 +2769,6 @@
"Or send invite link": "Or send invite link",
"Unnamed Space": "Unnamed Space",
"Invite to %(roomName)s": "Invite to %(roomName)s",
"Unnamed Room": "Unnamed Room",
"Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.",
"Invite someone using their name, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this space</a>.",
"Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.",
Expand Down
24 changes: 19 additions & 5 deletions src/utils/exportUtils/Exporter.ts
@@ -1,5 +1,5 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Copyright 2021 - 2022 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.
Expand All @@ -20,12 +20,13 @@ import { MatrixClient } from "matrix-js-sdk/src/client";
import { Direction } from "matrix-js-sdk/src/models/event-timeline";
import { saveAs } from "file-saver";
import { logger } from "matrix-js-sdk/src/logger";
import sanitizeFilename from "sanitize-filename";

import { MatrixClientPeg } from "../../MatrixClientPeg";
import { ExportType, IExportOptions } from "./exportUtils";
import { decryptFile } from "../DecryptFile";
import { mediaFromContent } from "../../customisations/Media";
import { formatFullDateNoDay } from "../../DateUtils";
import { formatFullDateNoDay, formatFullDateNoDayISO } from "../../DateUtils";
import { isVoiceMessage } from "../EventUtils";
import { IMediaEventContent } from "../../customisations/models/IMediaEventContent";
import { _t } from "../../languageHandler";
Expand Down Expand Up @@ -57,6 +58,10 @@ export default abstract class Exporter {
window.addEventListener("beforeunload", this.onBeforeUnload);
}

public get destinationFileName(): string {
return this.makeFileNameNoExtension(SdkConfig.get().brand) + ".zip";
}

protected onBeforeUnload(e: BeforeUnloadEvent): string {
e.preventDefault();
return e.returnValue = _t("Are you sure you want to exit during this export?");
Expand All @@ -75,10 +80,19 @@ export default abstract class Exporter {
this.files.push(file);
}

protected makeFileNameNoExtension(brand = "matrix"): string {
// First try to use the real name of the room, then a translated copy of a generic name,
// then finally hardcoded default to guarantee we'll have a name.
const safeRoomName = sanitizeFilename(this.room.name ?? _t("Unnamed Room")).trim() || "Unnamed Room";
const safeDate = formatFullDateNoDayISO(new Date())
.replace(/:/g, '-'); // ISO format automatically removes a lot of stuff for us
const safeBrand = sanitizeFilename(brand);
return `${safeBrand} - ${safeRoomName} - Chat Export - ${safeDate}`;
}

protected async downloadZIP(): Promise<string | void> {
const brand = SdkConfig.get().brand;
const filenameWithoutExt = `${brand} - Chat Export - ${formatFullDateNoDay(new Date())}`;
const filename = `${filenameWithoutExt}.zip`;
const filename = this.destinationFileName;
const filenameWithoutExt = filename.substring(0, filename.length - 4); // take off the .zip
const { default: JSZip } = await import('jszip');

const zip = new JSZip();
Expand Down
10 changes: 7 additions & 3 deletions src/utils/exportUtils/JSONExport.ts
@@ -1,5 +1,5 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Copyright 2021 - 2022 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.
Expand All @@ -20,7 +20,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { logger } from "matrix-js-sdk/src/logger";

import Exporter from "./Exporter";
import { formatFullDateNoDay, formatFullDateNoDayNoTime } from "../../DateUtils";
import { formatFullDateNoDayNoTime } from "../../DateUtils";
import { ExportType, IExportOptions } from "./exportUtils";
import { _t } from "../../languageHandler";
import { haveRendererForEvent } from "../../events/EventTileFactory";
Expand All @@ -38,6 +38,10 @@ export default class JSONExporter extends Exporter {
super(room, exportType, exportOptions, setProgressText);
}

public get destinationFileName(): string {
return this.makeFileNameNoExtension() + ".json";
}

protected createJSONString(): string {
const exportDate = formatFullDateNoDayNoTime(new Date());
const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
Expand Down Expand Up @@ -108,7 +112,7 @@ export default class JSONExporter extends Exporter {
this.addFile("export.json", new Blob([text]));
await this.downloadZIP();
} else {
const fileName = `matrix-export-${formatFullDateNoDay(new Date())}.json`;
const fileName = this.destinationFileName;
this.downloadPlainText(fileName, text);
}

Expand Down
9 changes: 6 additions & 3 deletions src/utils/exportUtils/PlainTextExport.ts
@@ -1,5 +1,5 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Copyright 2021 - 2022 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.
Expand All @@ -20,7 +20,6 @@ import { logger } from "matrix-js-sdk/src/logger";
import React from "react";

import Exporter from "./Exporter";
import { formatFullDateNoDay } from "../../DateUtils";
import { _t } from "../../languageHandler";
import { ExportType, IExportOptions } from "./exportUtils";
import { textForEvent } from "../../TextForEvent";
Expand All @@ -43,6 +42,10 @@ export default class PlainTextExporter extends Exporter {
: _t("Media omitted - file size limit exceeded");
}

public get destinationFileName(): string {
return this.makeFileNameNoExtension() + ".txt";
}

public textForReplyEvent = (content: IContent) => {
const REPLY_REGEX = /> <(.*?)>(.*?)\n\n(.*)/s;
const REPLY_SOURCE_MAX_LENGTH = 32;
Expand Down Expand Up @@ -137,7 +140,7 @@ export default class PlainTextExporter extends Exporter {
this.addFile("export.txt", new Blob([text]));
await this.downloadZIP();
} else {
const fileName = `matrix-export-${formatFullDateNoDay(new Date())}.txt`;
const fileName = this.destinationFileName;
this.downloadPlainText(fileName, text);
}

Expand Down
17 changes: 17 additions & 0 deletions test/test-utils/date.ts
@@ -0,0 +1,17 @@
/*
Copyright 2022 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.
*/

export const REPEATABLE_DATE = new Date(2022, 10, 17, 16, 58, 32, 517);
turt2live marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 8 additions & 1 deletion test/utils/DateUtils-test.ts
Expand Up @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { formatSeconds, formatRelativeTime, formatDuration } from "../../src/DateUtils";
import { formatSeconds, formatRelativeTime, formatDuration, formatFullDateNoDayISO } from "../../src/DateUtils";
import { REPEATABLE_DATE } from "../test-utils/date";

describe("formatSeconds", () => {
it("correctly formats time with hours", () => {
Expand Down Expand Up @@ -92,3 +93,9 @@ describe('formatDuration()', () => {
expect(formatDuration(input)).toEqual(expectedResult);
});
});

describe("formatFullDateNoDayISO", () => {
it("should return ISO format", () => {
expect(formatFullDateNoDayISO(REPEATABLE_DATE)).toEqual("2022-11-17T16:58:32.517Z");
});
});
53 changes: 53 additions & 0 deletions test/utils/exportUtils/HTMLExport-test.ts
@@ -0,0 +1,53 @@
/*
Copyright 2022 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 { mocked } from "jest-mock";

import { createTestClient, mkStubRoom } from "../../test-utils";
import { ExportType, IExportOptions } from "../../../src/utils/exportUtils/exportUtils";
import { REPEATABLE_DATE } from "../../test-utils/date";
import SdkConfig from "../../../src/SdkConfig";
import HTMLExporter from "../../../src/utils/exportUtils/HtmlExport";

describe("HTMLExport", () => {
beforeEach(() => {
jest.useFakeTimers('modern');
jest.setSystemTime(REPEATABLE_DATE);
});

afterEach(() => {
mocked(SdkConfig.get).mockRestore();
});

it("should have an SDK-branded destination file name", () => {
const roomName = "My / Test / Room: Welcome";
const client = createTestClient();
const stubOptions: IExportOptions = {
attachmentsIncluded: false,
maxSize: 50000000,
};
const stubRoom = mkStubRoom("!myroom:example.org", roomName, client);
const exporter = new HTMLExporter(stubRoom, ExportType.Timeline, stubOptions, () => {});

expect(exporter.destinationFileName).toMatchSnapshot();

jest.spyOn(SdkConfig, "get").mockImplementation(() => {
return { brand: "BrandedChat/WithSlashes/ForFun" };
});

expect(exporter.destinationFileName).toMatchSnapshot();
});
});
40 changes: 40 additions & 0 deletions test/utils/exportUtils/JSONExport-test.ts
@@ -0,0 +1,40 @@
/*
Copyright 2022 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 JSONExporter from "../../../src/utils/exportUtils/JSONExport";
import { createTestClient, mkStubRoom } from "../../test-utils";
import { ExportType, IExportOptions } from "../../../src/utils/exportUtils/exportUtils";
import { REPEATABLE_DATE } from "../../test-utils/date";

describe("JSONExport", () => {
beforeEach(() => {
jest.useFakeTimers('modern');
jest.setSystemTime(REPEATABLE_DATE);
});

it("should have a Matrix-branded destination file name", () => {
const roomName = "My / Test / Room: Welcome";
const client = createTestClient();
const stubOptions: IExportOptions = {
attachmentsIncluded: false,
maxSize: 50000000,
};
const stubRoom = mkStubRoom("!myroom:example.org", roomName, client);
const exporter = new JSONExporter(stubRoom, ExportType.Timeline, stubOptions, () => {});

expect(exporter.destinationFileName).toMatchSnapshot();
});
});
40 changes: 40 additions & 0 deletions test/utils/exportUtils/PlainTextExport-test.ts
@@ -0,0 +1,40 @@
/*
Copyright 2022 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 { createTestClient, mkStubRoom } from "../../test-utils";
import { ExportType, IExportOptions } from "../../../src/utils/exportUtils/exportUtils";
import PlainTextExporter from "../../../src/utils/exportUtils/PlainTextExport";
import { REPEATABLE_DATE } from "../../test-utils/date";

describe("PlainTextExport", () => {
beforeEach(() => {
jest.useFakeTimers('modern');
jest.setSystemTime(REPEATABLE_DATE);
});

it("should have a Matrix-branded destination file name", () => {
const roomName = "My / Test / Room: Welcome";
const client = createTestClient();
const stubOptions: IExportOptions = {
attachmentsIncluded: false,
maxSize: 50000000,
};
const stubRoom = mkStubRoom("!myroom:example.org", roomName, client);
const exporter = new PlainTextExporter(stubRoom, ExportType.Timeline, stubOptions, () => {});

expect(exporter.destinationFileName).toMatchSnapshot();
});
});
@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`HTMLExport should have an SDK-branded destination file name 1`] = `"Element - My Test Room Welcome - Chat Export - 2022-11-17T16-58-32.517Z.zip"`;

exports[`HTMLExport should have an SDK-branded destination file name 2`] = `"BrandedChatWithSlashesForFun - My Test Room Welcome - Chat Export - 2022-11-17T16-58-32.517Z.zip"`;
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`JSONExport should have a Matrix-branded destination file name 1`] = `"matrix - My Test Room Welcome - Chat Export - 2022-11-17T16-58-32.517Z.json"`;
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`PlainTextExport should have a Matrix-branded destination file name 1`] = `"matrix - My Test Room Welcome - Chat Export - 2022-11-17T16-58-32.517Z.txt"`;