This commit is contained in:
Jaiwanth 2021-06-30 14:08:22 +05:30
parent 551639811b
commit 08d886e9d1
12 changed files with 77 additions and 79 deletions

View file

@ -277,9 +277,9 @@ function textForMessageEvent(ev: MatrixEvent): () => string | null {
if (ev.getContent().msgtype === "m.emote") {
message = "* " + senderDisplayName + " " + message;
} else if (ev.getContent().msgtype === "m.image") {
message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName});
message = _t('%(senderDisplayName)s sent an image.', { senderDisplayName });
} 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;
return message;
};

View file

@ -194,7 +194,7 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> {
private getFileBody = () => {
if (this.props.forExport) return null;
return <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />;
}
};
render() {
const content = this.props.mxEvent.getContent();

View file

@ -234,7 +234,7 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, onClose }) => {
};
const onRoomExportClick = async () => {
const {default: ExportDialog} = await import("../dialogs/ExportDialog");
const { default: ExportDialog } = await import("../dialogs/ExportDialog");
Modal.createTrackedDialog('export room dialog', '', ExportDialog, {
room,

View file

@ -32,7 +32,6 @@ import RoomName from "../elements/RoomName";
import { PlaceCallType } from "../../../CallHandler";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.rooms.RoomHeader")
export default class RoomHeader extends React.Component {
static propTypes = {

View file

@ -8,7 +8,7 @@ import { mediaFromContent } from "../../customisations/Media";
import { formatFullDateNoDay } from "../../DateUtils";
import { MatrixClient } from "matrix-js-sdk";
import streamToZIP from "./ZipStream";
import * as ponyfill from "web-streams-polyfill/ponyfill"
import * as ponyfill from "web-streams-polyfill/ponyfill";
import "web-streams-polyfill/ponyfill"; // to support streams API for older browsers
type FileStream = {
@ -44,7 +44,7 @@ export default abstract class Exporter {
const file = {
name: filePath,
stream: () => blob.stream(),
}
};
this.files.push(file);
}
@ -52,7 +52,7 @@ export default abstract class Exporter {
const filename = `matrix-export-${formatFullDateNoDay(new Date())}.zip`;
// Support for older browsers
streamSaver.WritableStream = ponyfill.WritableStream
streamSaver.WritableStream = ponyfill.WritableStream;
// Create a writable stream to the directory
this.fileStream = streamSaver.createWriteStream(filename);
@ -72,9 +72,9 @@ export default abstract class Exporter {
if (this.cancelled) return this.cleanUp();
console.info("Writing to the file system...")
console.info("Writing to the file system...");
const reader = readableZipStream.getReader()
const reader = readableZipStream.getReader();
await this.pumpToFileStream(reader);
}
@ -93,7 +93,7 @@ export default abstract class Exporter {
protected async downloadPlainText(fileName: string, text: string): Promise<any> {
this.fileStream = streamSaver.createWriteStream(fileName);
this.writer = this.fileStream.getWriter()
this.writer = this.fileStream.getWriter();
const data = new TextEncoder().encode(text);
if (this.cancelled) return this.cleanUp();
await this.writer.write(data);
@ -142,7 +142,7 @@ export default abstract class Exporter {
return limit;
}
protected async getRequiredEvents():Promise<MatrixEvent[]> {
protected async getRequiredEvents(): Promise<MatrixEvent[]> {
const eventMapper = this.client.getEventMapper();
let prevToken: string|null = null;

View file

@ -1,9 +1,9 @@
import React from "react"
import React from "react";
import Exporter from "./Exporter";
import { mediaFromMxc } from "../../customisations/Media";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { renderToStaticMarkup } from 'react-dom/server'
import { renderToStaticMarkup } from "react-dom/server";
import { Layout } from "../../settings/Layout";
import { shouldFormContinuation } from "../../components/structures/MessagePanel";
import { formatFullDateNoDayNoTime, wantsDateSeparator } from "../../DateUtils";
@ -191,7 +191,7 @@ export default class HTMLExporter extends Exporter {
</section>
<div id="snackbar"/>
</body>
</html>`
</html>`;
}
protected getAvatarURL(event: MatrixEvent): string {
@ -236,32 +236,35 @@ export default class HTMLExporter extends Exporter {
const hasAvatar = !!this.getAvatarURL(mxEv);
if (hasAvatar) await this.saveAvatarIfNeeded(mxEv);
const eventTile = <div className="mx_Export_EventWrapper" id={mxEv.getId()}>
<MatrixClientContext.Provider value = {this.client}>
<EventTile
mxEvent={mxEv}
continuation={continuation}
isRedacted={mxEv.isRedacted()}
replacingEventId={mxEv.replacingEventId()}
forExport={true}
readReceipts={null}
readReceiptMap={null}
showUrlPreview={false}
checkUnmounting={() => false}
isTwelveHour={false}
last={false}
lastInSection={false}
permalinkCreator={this.permalinkCreator}
lastSuccessful={false}
isSelectedEvent={false}
getRelationsForEvent={null}
showReactions={false}
layout={Layout.Group}
enableFlair={false}
showReadReceipts={false}
/>
</MatrixClientContext.Provider>
</div>
const eventTile = (
<div className="mx_Export_EventWrapper" id={mxEv.getId()}>
<MatrixClientContext.Provider value={this.client}>
<EventTile
mxEvent={mxEv}
continuation={continuation}
isRedacted={mxEv.isRedacted()}
replacingEventId={mxEv.replacingEventId()}
forExport={true}
readReceipts={null}
readReceiptMap={null}
showUrlPreview={false}
checkUnmounting={() => false}
isTwelveHour={false}
last={false}
lastInSection={false}
permalinkCreator={this.permalinkCreator}
lastSuccessful={false}
isSelectedEvent={false}
getRelationsForEvent={null}
showReactions={false}
layout={Layout.Group}
enableFlair={false}
showReadReceipts={false}
/>
</MatrixClientContext.Provider>
</div>
);
let eventTileMarkup = renderToStaticMarkup(eventTile);
if (filePath) {
const mxc = mxEv.getContent().url || mxEv.getContent().file?.url;
@ -282,7 +285,7 @@ export default class HTMLExporter extends Exporter {
body: `*${text}*`,
format: "org.matrix.custom.html",
formatted_body: `<em>${text}</em>`,
}
};
const modifiedEvent = new MatrixEvent();
modifiedEvent.event = mxEv.event;
modifiedEvent.sender = mxEv.sender;
@ -367,7 +370,7 @@ export default class HTMLExporter extends Exporter {
if (this.cancelled) {
console.info("Export cancelled successfully");
} else {
console.info("Export successful!")
console.info("Export successful!");
console.log(`Exported ${res.length} events in ${(exportEnd - fetchStart)/1000} seconds`);
}

View file

@ -7,7 +7,6 @@ import { exportTypes } from "./exportUtils";
import { exportOptions } from "./exportUtils";
import { EventType } from "matrix-js-sdk/src/@types/event";
export default class JSONExporter extends Exporter {
protected totalSize: number;
protected messages: any[];
@ -32,7 +31,7 @@ export default class JSONExporter extends Exporter {
export_date: exportDate,
exported_by: exporterName,
messages: this.messages,
}
};
return JSON.stringify(jsonObject, null, 2);
}
@ -90,11 +89,11 @@ export default class JSONExporter extends Exporter {
if (this.cancelled) {
console.info("Export cancelled successfully");
} else {
console.info("Export successful!")
console.info("Export successful!");
console.log(`Exported ${res.length} events in ${(exportEnd - fetchStart)/1000} seconds`);
}
this.cleanUp()
this.cleanUp();
}
}

View file

@ -8,7 +8,6 @@ import { exportTypes } from "./exportUtils";
import { exportOptions } from "./exportUtils";
import { textForEvent } from "../../TextForEvent";
export default class PlainTextExporter extends Exporter {
protected totalSize: number;
protected mediaOmitText: string;
@ -21,7 +20,7 @@ export default class PlainTextExporter extends Exporter {
: _t("Media omitted - file size limit exceeded");
}
protected textForReplyEvent = (ev : MatrixEvent) => {
protected textForReplyEvent = (ev: MatrixEvent) => {
const REPLY_REGEX = /> <(.*?)>(.*?)\n\n(.*)/;
const REPLY_SOURCE_MAX_LENGTH = 32;
const content = ev.getContent();
@ -36,7 +35,7 @@ export default class PlainTextExporter extends Exporter {
rplSource = match[2].substring(1, REPLY_SOURCE_MAX_LENGTH);
// Get the first non-blank line from the source.
const lines = rplSource.split('\n').filter((line) => !/^\s*$/.test(line))
const lines = rplSource.split('\n').filter((line) => !/^\s*$/.test(line));
if (lines.length > 0) {
// Cut to a maximum length.
rplSource = lines[0].substring(0, REPLY_SOURCE_MAX_LENGTH);
@ -52,7 +51,7 @@ export default class PlainTextExporter extends Exporter {
}
return `<${rplName}${rplSource}> ${rplText}`;
}
};
protected _textForEvent = async (mxEv: MatrixEvent) => {
const senderDisplayName = mxEv.sender && mxEv.sender.name ? mxEv.sender.name : mxEv.getSender();
@ -76,7 +75,7 @@ export default class PlainTextExporter extends Exporter {
}
if (this.isReply(mxEv)) return senderDisplayName + ": " + this.textForReplyEvent(mxEv) + mediaText;
else return textForEvent(mxEv) + mediaText;
}
};
protected async createOutput(events: MatrixEvent[]) {
let content = "";
@ -115,7 +114,7 @@ export default class PlainTextExporter extends Exporter {
if (this.cancelled) {
console.info("Export cancelled successfully");
} else {
console.info("Export successful!")
console.info("Export successful!");
console.log(`Exported ${res.length} events in ${(exportEnd - fetchStart)/1000} seconds`);
}

View file

@ -13,7 +13,6 @@ type TypedArray =
| Float32Array
| Float64Array;
/**
* 32-bit cyclic redundancy check, or CRC-32 - checksum
*/
@ -23,8 +22,8 @@ class Crc32 {
constructor() {
this.crc = -1;
this.table = (() => {
let i
let j
let i;
let j;
let t;
const table = [];
@ -37,8 +36,8 @@ class Crc32 {
}
table[i] = t;
}
return table
})()
return table;
})();
}
append(data: TypedArray) {
@ -55,25 +54,24 @@ class Crc32 {
}
}
type DataHelper = {
array: Uint8Array,
view: DataView,
}
};
const getDataHelper = (byteLength: number): DataHelper => {
const uint8 = new Uint8Array(byteLength)
const uint8 = new Uint8Array(byteLength);
return {
array: uint8,
view: new DataView(uint8.buffer),
};
}
};
type FileLike = File & {
directory: string,
comment: string,
stream(): ReadableStream,
}
};
type ZipObj = {
crc?: Crc32,
@ -88,7 +86,7 @@ type ZipObj = {
fileLike: FileLike,
level: number,
directory: boolean,
}
};
const pump = (zipObj: ZipObj) => zipObj.reader ? zipObj.reader.read().then(chunk => {
if (zipObj.crc) {
@ -124,7 +122,7 @@ export default function streamToZIP(underlyingSource: UnderlyingSource) {
desiredSize: null,
error(err) {
console.error(err)
console.error(err);
},
enqueue(fileLike: FileLike) {
@ -155,8 +153,8 @@ export default function streamToZIP(underlyingSource: UnderlyingSource) {
offset,
writeHeader() {
const header = getDataHelper(26)
const data = getDataHelper(30 + nameBuf.length)
const header = getDataHelper(26);
const data = getDataHelper(30 + nameBuf.length);
zipObject.offset = offset;
zipObject.header = header;
@ -200,10 +198,10 @@ export default function streamToZIP(underlyingSource: UnderlyingSource) {
ctrl.enqueue(footer.array);
offset += zipObject.compressedLength + 16;
next()
next();
},
fileLike,
}
};
if (!activeZipObject) {
activeZipObject = zipObject;
@ -220,11 +218,11 @@ export default function streamToZIP(underlyingSource: UnderlyingSource) {
if (!activeZipObject) closeZip();
closed = true;
},
}
};
function closeZip() {
let length = 0;
let index = 0
let index = 0;
let indexFilename;
let file;
@ -232,7 +230,7 @@ export default function streamToZIP(underlyingSource: UnderlyingSource) {
file = files[filenames[indexFilename]];
length += 46 + file.nameBuf.length + file.comment.length;
}
const data = getDataHelper(length + 22)
const data = getDataHelper(length + 22);
for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
file = files[filenames[indexFilename]];
data.view.setUint32(index, 0x504b0102);
@ -275,7 +273,7 @@ export default function streamToZIP(underlyingSource: UnderlyingSource) {
return processNextChunk() || (
underlyingSource.pull &&
Promise.resolve(underlyingSource.pull(zipWriter))
)
);
},
});
}

View file

@ -19464,7 +19464,7 @@ a.mx_RoomPreviewBar_inviter {
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
}
`
`;
const customCSS = `
#snackbar {
display: flex;
@ -19567,8 +19567,8 @@ img {
overflow: hidden;
}
`
`;
const markdownCSS = `.markdown-body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;color:#333;overflow:hidden;font-family:'Helvetica Neue',Helvetica,'Segoe UI',Arial,freesans,sans-serif;font-size:16px;line-height:1.6;word-wrap:break-word}.markdown-body *{-moz-box-sizing:border-box;box-sizing:border-box}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a{background:0 0;color:#4183c4;text-decoration:none}.markdown-body a:active,.markdown-body a:hover{outline:0}.markdown-body a:active,.markdown-body a:focus,.markdown-body a:hover{text-decoration:underline}.markdown-body strong{font-weight:700}.markdown-body em{font-style:italic}.markdown-body blockquote,.markdown-body dl,.markdown-body ol,.markdown-body p,.markdown-body pre,.markdown-body table,.markdown-body ul{margin-top:0;margin-bottom:16px}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{font-family:'Helvetica Neue',Helvetica,'Segoe UI',Arial,freesans,sans-serif;position:relative;margin-top:1em;margin-bottom:16px;font-weight:700;line-height:1.4}.markdown-body h1,.markdown-body h2{padding-bottom:.3em;border-bottom:1px solid #eee}.markdown-body h1{font-size:2.25em;line-height:1.2}.markdown-body h2{font-size:1.75em;line-height:1.225}.markdown-body h3{font-size:1.5em}.markdown-body h4{font-size:1.25em}.markdown-body h5{font-size:1em}.markdown-body h6{font-size:1em;color:#777}.markdown-body code,.markdown-body kbd,.markdown-body pre{font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace}.markdown-body code{padding:.2em 0;margin:0;font-size:85%;background-color:rgba(0,0,0,.04);border-radius:3px}.markdown-body code:after,.markdown-body code:before{letter-spacing:-.2em;content:'\\00a0'}.markdown-body pre{word-wrap:normal;padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f7f7f7;border-radius:3px}.markdown-body pre code{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;word-wrap:normal;background:0 0}.markdown-body pre code:after,.markdown-body pre code:before{content:normal}.markdown-body pre>code{font-size:1em;word-break:normal;white-space:pre;border:0}.markdown-body kbd{background-color:#e7e7e7;background-image:-webkit-linear-gradient(#fefefe,#e7e7e7);background-image:linear-gradient(#fefefe,#e7e7e7);background-repeat:repeat-x;display:inline-block;padding:5px 5px 1px;margin:0 1px;font-size:11px;line-height:10px;color:#000;border:1px solid #cfcfcf;border-radius:2px;box-shadow:0 1px 0 #ccc}.markdown-body hr:after,.markdown-body hr:before{display:table;content:''}.markdown-body input{color:inherit;font:inherit;margin:0;font-size:13px;line-height:1.4;font-family:Helvetica,Arial,freesans,clean,sans-serif,'Segoe UI Emoji','Segoe UI Symbol'}.markdown-body input[disabled]{cursor:default}.markdown-body input[type=checkbox]{-moz-box-sizing:border-box;box-sizing:border-box;padding:0}.markdown-body blockquote{margin:0 0 16px;padding:0 15px;color:#777;border-left:4px solid #ddd}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body img{border:0;max-width:100%;-moz-box-sizing:border-box;box-sizing:border-box}.markdown-body hr{-moz-box-sizing:content-box;box-sizing:content-box;overflow:hidden;background:#e7e7e7;height:4px;padding:0;margin:16px 0;border:0}.markdown-body hr:after{clear:both}.markdown-body td,.markdown-body th{padding:0}.markdown-body table{border-collapse:collapse;border-spacing:0;display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all}.markdown-body table td,.markdown-body table th{padding:6px 13px;border:1px solid #ddd}.markdown-body table th{font-weight:700}.markdown-body table tr{background-color:#fff;border-top:1px solid #ccc}.markdown-body table tr:nth-child(2n){background-color:#f8f8f8}.markdown-body ol,.markdown-body ul{padding:0 0 0 2em}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:0;margin-bottom:0}.markdown-body ol ol ol,.markdown-body ol ul ol,.markdown-body ul ol ol,.markdown-body ul ul ol{list-style-type:lower-alpha}.markdown-body li>p{margin-top:16px}.markdown-body dd{margin-left:0}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:700}.markdown-body dl dd{padding:0 16px;margin-bottom:16px}`
const markdownCSS = `.markdown-body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;color:#333;overflow:hidden;font-family:'Helvetica Neue',Helvetica,'Segoe UI',Arial,freesans,sans-serif;font-size:16px;line-height:1.6;word-wrap:break-word}.markdown-body *{-moz-box-sizing:border-box;box-sizing:border-box}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a{background:0 0;color:#4183c4;text-decoration:none}.markdown-body a:active,.markdown-body a:hover{outline:0}.markdown-body a:active,.markdown-body a:focus,.markdown-body a:hover{text-decoration:underline}.markdown-body strong{font-weight:700}.markdown-body em{font-style:italic}.markdown-body blockquote,.markdown-body dl,.markdown-body ol,.markdown-body p,.markdown-body pre,.markdown-body table,.markdown-body ul{margin-top:0;margin-bottom:16px}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{font-family:'Helvetica Neue',Helvetica,'Segoe UI',Arial,freesans,sans-serif;position:relative;margin-top:1em;margin-bottom:16px;font-weight:700;line-height:1.4}.markdown-body h1,.markdown-body h2{padding-bottom:.3em;border-bottom:1px solid #eee}.markdown-body h1{font-size:2.25em;line-height:1.2}.markdown-body h2{font-size:1.75em;line-height:1.225}.markdown-body h3{font-size:1.5em}.markdown-body h4{font-size:1.25em}.markdown-body h5{font-size:1em}.markdown-body h6{font-size:1em;color:#777}.markdown-body code,.markdown-body kbd,.markdown-body pre{font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace}.markdown-body code{padding:.2em 0;margin:0;font-size:85%;background-color:rgba(0,0,0,.04);border-radius:3px}.markdown-body code:after,.markdown-body code:before{letter-spacing:-.2em;content:'\\00a0'}.markdown-body pre{word-wrap:normal;padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f7f7f7;border-radius:3px}.markdown-body pre code{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;word-wrap:normal;background:0 0}.markdown-body pre code:after,.markdown-body pre code:before{content:normal}.markdown-body pre>code{font-size:1em;word-break:normal;white-space:pre;border:0}.markdown-body kbd{background-color:#e7e7e7;background-image:-webkit-linear-gradient(#fefefe,#e7e7e7);background-image:linear-gradient(#fefefe,#e7e7e7);background-repeat:repeat-x;display:inline-block;padding:5px 5px 1px;margin:0 1px;font-size:11px;line-height:10px;color:#000;border:1px solid #cfcfcf;border-radius:2px;box-shadow:0 1px 0 #ccc}.markdown-body hr:after,.markdown-body hr:before{display:table;content:''}.markdown-body input{color:inherit;font:inherit;margin:0;font-size:13px;line-height:1.4;font-family:Helvetica,Arial,freesans,clean,sans-serif,'Segoe UI Emoji','Segoe UI Symbol'}.markdown-body input[disabled]{cursor:default}.markdown-body input[type=checkbox]{-moz-box-sizing:border-box;box-sizing:border-box;padding:0}.markdown-body blockquote{margin:0 0 16px;padding:0 15px;color:#777;border-left:4px solid #ddd}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body img{border:0;max-width:100%;-moz-box-sizing:border-box;box-sizing:border-box}.markdown-body hr{-moz-box-sizing:content-box;box-sizing:content-box;overflow:hidden;background:#e7e7e7;height:4px;padding:0;margin:16px 0;border:0}.markdown-body hr:after{clear:both}.markdown-body td,.markdown-body th{padding:0}.markdown-body table{border-collapse:collapse;border-spacing:0;display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all}.markdown-body table td,.markdown-body table th{padding:6px 13px;border:1px solid #ddd}.markdown-body table th{font-weight:700}.markdown-body table tr{background-color:#fff;border-top:1px solid #ccc}.markdown-body table tr:nth-child(2n){background-color:#f8f8f8}.markdown-body ol,.markdown-body ul{padding:0 0 0 2em}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:0;margin-bottom:0}.markdown-body ol ol ol,.markdown-body ol ul ol,.markdown-body ul ol ol,.markdown-body ul ul ol{list-style-type:lower-alpha}.markdown-body li>p{margin-top:16px}.markdown-body dd{margin-left:0}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:700}.markdown-body dl dd{padding:0 16px;margin-bottom:16px}`;
export default lightCSS + markdownCSS + customCSS;

View file

@ -24,4 +24,4 @@ document.querySelectorAll('.mx_reply_anchor').forEach(element => {
})
}
`
`;

View file

@ -22,7 +22,7 @@ export const textForFormat = (format: string): string => {
case exportFormats.PLAIN_TEXT:
return _t("Plain Text");
}
}
};
export const textForType = (type: string): string => {
switch (type) {
@ -35,7 +35,7 @@ export const textForType = (type: string): string => {
// case exportTypes.START_DATE:
// return _t("From a specific date");
}
}
};
export interface exportOptions {
startDate?: number;