Apply prettier formatting
This commit is contained in:
@@ -48,9 +48,10 @@ export default abstract class Exporter {
|
||||
protected exportOptions: IExportOptions,
|
||||
protected setProgressText: React.Dispatch<React.SetStateAction<string>>,
|
||||
) {
|
||||
if (exportOptions.maxSize < 1 * 1024 * 1024|| // Less than 1 MB
|
||||
if (
|
||||
exportOptions.maxSize < 1 * 1024 * 1024 || // Less than 1 MB
|
||||
exportOptions.maxSize > 8000 * 1024 * 1024 || // More than 8 GB
|
||||
exportOptions.numberOfMessages > 10**8
|
||||
exportOptions.numberOfMessages > 10 ** 8
|
||||
) {
|
||||
throw new Error("Invalid export options");
|
||||
}
|
||||
@@ -64,7 +65,7 @@ export default abstract class Exporter {
|
||||
|
||||
protected onBeforeUnload(e: BeforeUnloadEvent): string {
|
||||
e.preventDefault();
|
||||
return e.returnValue = _t("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 {
|
||||
@@ -84,8 +85,7 @@ export default abstract class Exporter {
|
||||
// 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 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}`;
|
||||
}
|
||||
@@ -93,7 +93,7 @@ export default abstract class Exporter {
|
||||
protected async downloadZIP(): Promise<string | void> {
|
||||
const filename = this.destinationFileName;
|
||||
const filenameWithoutExt = filename.substring(0, filename.length - 4); // take off the .zip
|
||||
const { default: JSZip } = await import('jszip');
|
||||
const { default: JSZip } = await import("jszip");
|
||||
|
||||
const zip = new JSZip();
|
||||
// Create a writable stream to the directory
|
||||
@@ -125,13 +125,9 @@ export default abstract class Exporter {
|
||||
|
||||
protected setEventMetadata(event: MatrixEvent): MatrixEvent {
|
||||
const roomState = this.client.getRoom(this.room.roomId).currentState;
|
||||
event.sender = roomState.getSentinelMember(
|
||||
event.getSender(),
|
||||
);
|
||||
event.sender = roomState.getSentinelMember(event.getSender());
|
||||
if (event.getType() === "m.room.member") {
|
||||
event.target = roomState.getSentinelMember(
|
||||
event.getStateKey(),
|
||||
);
|
||||
event.target = roomState.getSentinelMember(event.getStateKey());
|
||||
}
|
||||
return event;
|
||||
}
|
||||
@@ -146,7 +142,7 @@ export default abstract class Exporter {
|
||||
limit = 40;
|
||||
break;
|
||||
default:
|
||||
limit = 10**8;
|
||||
limit = 10 ** 8;
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
@@ -154,7 +150,7 @@ export default abstract class Exporter {
|
||||
protected async getRequiredEvents(): Promise<MatrixEvent[]> {
|
||||
const eventMapper = this.client.getEventMapper();
|
||||
|
||||
let prevToken: string|null = null;
|
||||
let prevToken: string | null = null;
|
||||
let limit = this.getLimit();
|
||||
const events: MatrixEvent[] = [];
|
||||
|
||||
@@ -188,26 +184,30 @@ export default abstract class Exporter {
|
||||
}
|
||||
|
||||
if (this.exportType === ExportType.LastNMessages) {
|
||||
this.updateProgress(_t("Fetched %(count)s events out of %(total)s", {
|
||||
count: events.length,
|
||||
total: this.exportOptions.numberOfMessages,
|
||||
}));
|
||||
this.updateProgress(
|
||||
_t("Fetched %(count)s events out of %(total)s", {
|
||||
count: events.length,
|
||||
total: this.exportOptions.numberOfMessages,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
this.updateProgress(_t("Fetched %(count)s events so far", {
|
||||
count: events.length,
|
||||
}));
|
||||
this.updateProgress(
|
||||
_t("Fetched %(count)s events so far", {
|
||||
count: events.length,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
prevToken = res.end;
|
||||
}
|
||||
// Reverse the events so that we preserve the order
|
||||
for (let i = 0; i < Math.floor(events.length/2); i++) {
|
||||
for (let i = 0; i < Math.floor(events.length / 2); i++) {
|
||||
[events[i], events[events.length - i - 1]] = [events[events.length - i - 1], events[i]];
|
||||
}
|
||||
|
||||
const decryptionPromises = events
|
||||
.filter(event => event.isEncrypted())
|
||||
.map(event => {
|
||||
.filter((event) => event.isEncrypted())
|
||||
.map((event) => {
|
||||
return this.client.decryptEventIfNeeded(event, {
|
||||
isRetry: true,
|
||||
emit: false,
|
||||
@@ -242,11 +242,11 @@ export default abstract class Exporter {
|
||||
}
|
||||
|
||||
public splitFileName(file: string): string[] {
|
||||
const lastDot = file.lastIndexOf('.');
|
||||
const lastDot = file.lastIndexOf(".");
|
||||
if (lastDot === -1) return [file, ""];
|
||||
const fileName = file.slice(0, lastDot);
|
||||
const ext = file.slice(lastDot + 1);
|
||||
return [fileName, '.' + ext];
|
||||
return [fileName, "." + ext];
|
||||
}
|
||||
|
||||
public getFilePath(event: MatrixEvent): string {
|
||||
@@ -271,7 +271,7 @@ export default abstract class Exporter {
|
||||
if (event.getType() === "m.sticker") fileExt = ".png";
|
||||
if (isVoiceMessage(event)) fileExt = ".ogg";
|
||||
|
||||
return fileDirectory + "/" + fileName + '-' + fileDate + fileExt;
|
||||
return fileDirectory + "/" + fileName + "-" + fileDate + fileExt;
|
||||
}
|
||||
|
||||
protected isReply(event: MatrixEvent): boolean {
|
||||
|
||||
@@ -106,31 +106,27 @@ export default class HTMLExporter extends Exporter {
|
||||
|
||||
const exportedText = renderToStaticMarkup(
|
||||
<p>
|
||||
{ _t(
|
||||
{_t(
|
||||
"This is the start of export of <roomName/>. Exported by <exporterDetails/> at %(exportDate)s.",
|
||||
{
|
||||
exportDate,
|
||||
},
|
||||
{
|
||||
roomName: () => <b>{ this.room.name }</b>,
|
||||
roomName: () => <b>{this.room.name}</b>,
|
||||
exporterDetails: () => (
|
||||
<a
|
||||
href={`https://matrix.to/#/${exporter}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{ exporterName ? (
|
||||
<a href={`https://matrix.to/#/${exporter}`} target="_blank" rel="noopener noreferrer">
|
||||
{exporterName ? (
|
||||
<>
|
||||
<b>{ exporterName }</b>
|
||||
{ " (" + exporter + ")" }
|
||||
<b>{exporterName}</b>
|
||||
{" (" + exporter + ")"}
|
||||
</>
|
||||
) : (
|
||||
<b>{ exporter }</b>
|
||||
) }
|
||||
<b>{exporter}</b>
|
||||
)}
|
||||
</a>
|
||||
),
|
||||
},
|
||||
) }
|
||||
)}
|
||||
</p>,
|
||||
);
|
||||
|
||||
@@ -224,12 +220,7 @@ export default class HTMLExporter extends Exporter {
|
||||
protected getAvatarURL(event: MatrixEvent): string {
|
||||
const member = event.sender;
|
||||
return (
|
||||
member.getMxcAvatarUrl() &&
|
||||
mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(
|
||||
30,
|
||||
30,
|
||||
"crop",
|
||||
)
|
||||
member.getMxcAvatarUrl() && mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(30, 30, "crop")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -241,7 +232,7 @@ export default class HTMLExporter extends Exporter {
|
||||
this.avatars.set(member.userId, true);
|
||||
const image = await fetch(avatarUrl);
|
||||
const blob = await image.blob();
|
||||
this.addFile(`users/${member.userId.replace(/:/g, '-')}.png`, blob);
|
||||
this.addFile(`users/${member.userId.replace(/:/g, "-")}.png`, blob);
|
||||
} catch (err) {
|
||||
logger.log("Failed to fetch user's avatar" + err);
|
||||
}
|
||||
@@ -264,32 +255,34 @@ export default class HTMLExporter extends Exporter {
|
||||
}
|
||||
|
||||
public getEventTile(mxEv: MatrixEvent, continuation: boolean) {
|
||||
return <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}
|
||||
alwaysShowTimestamps={true}
|
||||
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}
|
||||
showReadReceipts={false}
|
||||
/>
|
||||
</MatrixClientContext.Provider>
|
||||
</div>;
|
||||
return (
|
||||
<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}
|
||||
alwaysShowTimestamps={true}
|
||||
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}
|
||||
showReadReceipts={false}
|
||||
/>
|
||||
</MatrixClientContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected async getEventTileMarkup(mxEv: MatrixEvent, continuation: boolean, filePath?: string) {
|
||||
@@ -305,11 +298,8 @@ export default class HTMLExporter extends Exporter {
|
||||
) {
|
||||
// to linkify textual events, we'll need lifecycle methods which won't be invoked in renderToString
|
||||
// So, we'll have to render the component into a temporary root element
|
||||
const tempRoot = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
EventTile,
|
||||
tempRoot,
|
||||
);
|
||||
const tempRoot = document.createElement("div");
|
||||
ReactDOM.render(EventTile, tempRoot);
|
||||
eventTileMarkup = tempRoot.innerHTML;
|
||||
} else {
|
||||
eventTileMarkup = renderToStaticMarkup(EventTile);
|
||||
@@ -319,17 +309,17 @@ export default class HTMLExporter extends Exporter {
|
||||
const mxc = mxEv.getContent().url ?? mxEv.getContent().file?.url;
|
||||
eventTileMarkup = eventTileMarkup.split(mxc).join(filePath);
|
||||
}
|
||||
eventTileMarkup = eventTileMarkup.replace(/<span class="mx_MFileBody_info_icon".*?>.*?<\/span>/, '');
|
||||
eventTileMarkup = eventTileMarkup.replace(/<span class="mx_MFileBody_info_icon".*?>.*?<\/span>/, "");
|
||||
if (hasAvatar) {
|
||||
eventTileMarkup = eventTileMarkup.replace(
|
||||
encodeURI(this.getAvatarURL(mxEv)).replace(/&/g, '&'),
|
||||
encodeURI(this.getAvatarURL(mxEv)).replace(/&/g, "&"),
|
||||
`users/${mxEv.sender.userId.replace(/:/g, "-")}.png`,
|
||||
);
|
||||
}
|
||||
return eventTileMarkup;
|
||||
}
|
||||
|
||||
protected createModifiedEvent(text: string, mxEv: MatrixEvent, italic=true) {
|
||||
protected createModifiedEvent(text: string, mxEv: MatrixEvent, italic = true) {
|
||||
const modifiedContent = {
|
||||
msgtype: "m.text",
|
||||
body: `${text}`,
|
||||
@@ -337,8 +327,8 @@ export default class HTMLExporter extends Exporter {
|
||||
formatted_body: `${text}`,
|
||||
};
|
||||
if (italic) {
|
||||
modifiedContent.formatted_body = '<em>' + modifiedContent.formatted_body + '</em>';
|
||||
modifiedContent.body = '*' + modifiedContent.body + '*';
|
||||
modifiedContent.formatted_body = "<em>" + modifiedContent.formatted_body + "</em>";
|
||||
modifiedContent.body = "*" + modifiedContent.body + "*";
|
||||
}
|
||||
const modifiedEvent = new MatrixEvent();
|
||||
modifiedEvent.event = mxEv.event;
|
||||
@@ -402,15 +392,20 @@ export default class HTMLExporter extends Exporter {
|
||||
let prevEvent = null;
|
||||
for (let i = start; i < Math.min(start + 1000, events.length); i++) {
|
||||
const event = events[i];
|
||||
this.updateProgress(_t("Processing event %(number)s out of %(total)s", {
|
||||
number: i + 1,
|
||||
total: events.length,
|
||||
}), false, true);
|
||||
this.updateProgress(
|
||||
_t("Processing event %(number)s out of %(total)s", {
|
||||
number: i + 1,
|
||||
total: events.length,
|
||||
}),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
if (this.cancelled) return this.cleanUp();
|
||||
if (!haveRendererForEvent(event, false)) continue;
|
||||
|
||||
content += this.needsDateSeparator(event, prevEvent) ? this.getDateSeparator(event) : "";
|
||||
const shouldBeJoined = !this.needsDateSeparator(event, prevEvent) &&
|
||||
const shouldBeJoined =
|
||||
!this.needsDateSeparator(event, prevEvent) &&
|
||||
shouldFormContinuation(prevEvent, event, false, this.threadsEnabled);
|
||||
const body = await this.createMessageBody(event, shouldBeJoined);
|
||||
this.totalSize += Buffer.byteLength(body);
|
||||
@@ -427,10 +422,14 @@ export default class HTMLExporter extends Exporter {
|
||||
const res = await this.getRequiredEvents();
|
||||
const fetchEnd = performance.now();
|
||||
|
||||
this.updateProgress(_t("Fetched %(count)s events in %(seconds)ss", {
|
||||
count: res.length,
|
||||
seconds: (fetchEnd - fetchStart) / 1000,
|
||||
}), true, false);
|
||||
this.updateProgress(
|
||||
_t("Fetched %(count)s events in %(seconds)ss", {
|
||||
count: res.length,
|
||||
seconds: (fetchEnd - fetchStart) / 1000,
|
||||
}),
|
||||
true,
|
||||
false,
|
||||
);
|
||||
|
||||
this.updateProgress(_t("Creating HTML..."));
|
||||
|
||||
@@ -438,8 +437,8 @@ export default class HTMLExporter extends Exporter {
|
||||
for (let page = 0; page < res.length / 1000; page++) {
|
||||
const html = await this.createHTML(res, page * 1000);
|
||||
const document = new DOMParser().parseFromString(html, "text/html");
|
||||
document.querySelectorAll("*").forEach(element => {
|
||||
element.classList.forEach(c => usedClasses.add(c));
|
||||
document.querySelectorAll("*").forEach((element) => {
|
||||
element.classList.forEach((c) => usedClasses.add(c));
|
||||
});
|
||||
this.addFile(`messages${page ? page + 1 : ""}.html`, new Blob([html]));
|
||||
}
|
||||
@@ -456,10 +455,12 @@ export default class HTMLExporter extends Exporter {
|
||||
logger.info("Export cancelled successfully");
|
||||
} else {
|
||||
this.updateProgress(_t("Export successful!"));
|
||||
this.updateProgress(_t("Exported %(count)s events in %(seconds)s seconds", {
|
||||
count: res.length,
|
||||
seconds: (exportEnd - fetchStart) / 1000,
|
||||
}));
|
||||
this.updateProgress(
|
||||
_t("Exported %(count)s events in %(seconds)s seconds", {
|
||||
count: res.length,
|
||||
seconds: (exportEnd - fetchStart) / 1000,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
this.cleanUp();
|
||||
|
||||
@@ -84,10 +84,14 @@ export default class JSONExporter extends Exporter {
|
||||
protected async createOutput(events: MatrixEvent[]) {
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const event = events[i];
|
||||
this.updateProgress(_t("Processing event %(number)s out of %(total)s", {
|
||||
number: i + 1,
|
||||
total: events.length,
|
||||
}), false, true);
|
||||
this.updateProgress(
|
||||
_t("Processing event %(number)s out of %(total)s", {
|
||||
number: i + 1,
|
||||
total: events.length,
|
||||
}),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
if (this.cancelled) return this.cleanUp();
|
||||
if (!haveRendererForEvent(event, false)) continue;
|
||||
this.messages.push(await this.getJSONString(event));
|
||||
@@ -103,7 +107,7 @@ export default class JSONExporter extends Exporter {
|
||||
const res = await this.getRequiredEvents();
|
||||
const fetchEnd = performance.now();
|
||||
|
||||
logger.log(`Fetched ${res.length} events in ${(fetchEnd - fetchStart)/1000}s`);
|
||||
logger.log(`Fetched ${res.length} events in ${(fetchEnd - fetchStart) / 1000}s`);
|
||||
|
||||
logger.info("Creating output...");
|
||||
const text = await this.createOutput(res);
|
||||
@@ -122,10 +126,9 @@ export default class JSONExporter extends Exporter {
|
||||
logger.info("Export cancelled successfully");
|
||||
} else {
|
||||
logger.info("Export successful!");
|
||||
logger.log(`Exported ${res.length} events in ${(exportEnd - fetchStart)/1000} seconds`);
|
||||
logger.log(`Exported ${res.length} events in ${(exportEnd - fetchStart) / 1000} seconds`);
|
||||
}
|
||||
|
||||
this.cleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export default class PlainTextExporter extends Exporter {
|
||||
|
||||
rplSource = match[2].substring(1);
|
||||
// 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);
|
||||
@@ -111,10 +111,14 @@ export default class PlainTextExporter extends Exporter {
|
||||
let content = "";
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const event = events[i];
|
||||
this.updateProgress(_t("Processing event %(number)s out of %(total)s", {
|
||||
number: i + 1,
|
||||
total: events.length,
|
||||
}), false, true);
|
||||
this.updateProgress(
|
||||
_t("Processing event %(number)s out of %(total)s", {
|
||||
number: i + 1,
|
||||
total: events.length,
|
||||
}),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
if (this.cancelled) return this.cleanUp();
|
||||
if (!haveRendererForEvent(event, false)) continue;
|
||||
const textForEvent = await this.plainTextForEvent(event);
|
||||
@@ -131,7 +135,7 @@ export default class PlainTextExporter extends Exporter {
|
||||
const res = await this.getRequiredEvents();
|
||||
const fetchEnd = performance.now();
|
||||
|
||||
logger.log(`Fetched ${res.length} events in ${(fetchEnd - fetchStart)/1000}s`);
|
||||
logger.log(`Fetched ${res.length} events in ${(fetchEnd - fetchStart) / 1000}s`);
|
||||
|
||||
this.updateProgress(_t("Creating output..."));
|
||||
const text = await this.createOutput(res);
|
||||
@@ -150,10 +154,9 @@ export default class PlainTextExporter extends Exporter {
|
||||
logger.info("Export cancelled successfully");
|
||||
} else {
|
||||
logger.info("Export successful!");
|
||||
logger.log(`Exported ${res.length} events in ${(exportEnd - fetchStart)/1000} seconds`);
|
||||
logger.log(`Exported ${res.length} events in ${(exportEnd - fetchStart) / 1000} seconds`);
|
||||
}
|
||||
|
||||
this.cleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ async function getRulesFromCssFile(path: string): Promise<CSSStyleSheet> {
|
||||
// doesn't cull rules which won't apply due to the full selector not matching but gets rid of a LOT of cruft anyway.
|
||||
const getExportCSS = async (usedClasses: Set<string>): Promise<string> => {
|
||||
// only include bundle.css and the data-mx-theme=light styling
|
||||
const stylesheets = Array.from(document.styleSheets).filter(s => {
|
||||
const stylesheets = Array.from(document.styleSheets).filter((s) => {
|
||||
return s.href?.endsWith("bundle.css") || isLightTheme(s);
|
||||
});
|
||||
|
||||
@@ -70,12 +70,14 @@ const getExportCSS = async (usedClasses: Set<string>): Promise<string> => {
|
||||
const selectorText = (rule as CSSStyleRule).selectorText;
|
||||
|
||||
// only skip the rule if all branches (,) of the selector are redundant
|
||||
if (selectorText?.split(",").every(selector => {
|
||||
const classes = selector.match(cssSelectorTextClassesRegex);
|
||||
if (classes && !classes.every(c => usedClasses.has(c.substring(1)))) {
|
||||
return true; // signal as a redundant selector
|
||||
}
|
||||
})) {
|
||||
if (
|
||||
selectorText?.split(",").every((selector) => {
|
||||
const classes = selector.match(cssSelectorTextClassesRegex);
|
||||
if (classes && !classes.every((c) => usedClasses.has(c.substring(1)))) {
|
||||
return true; // signal as a redundant selector
|
||||
}
|
||||
})
|
||||
) {
|
||||
continue; // skip this rule as it is redundant
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,8 @@ limitations under the License.
|
||||
bottom: 30px;
|
||||
font-size: 17px;
|
||||
padding: 6px 16px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir,
|
||||
segoe ui, helvetica neue, helvetica, Ubuntu, roboto, noto, arial,
|
||||
sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu,
|
||||
roboto, noto, arial, sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.43;
|
||||
border-radius: 4px;
|
||||
@@ -126,7 +125,6 @@ a.mx_reply_anchor:hover {
|
||||
|
||||
.mx_RedactedBody,
|
||||
.mx_HiddenBody {
|
||||
|
||||
padding-left: unset;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,10 +33,9 @@ function showToast(text) {
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
document.querySelectorAll('.mx_reply_anchor').forEach(element => {
|
||||
element.addEventListener('click', event => {
|
||||
document.querySelectorAll(".mx_reply_anchor").forEach((element) => {
|
||||
element.addEventListener("click", (event) => {
|
||||
showToastIfNeeded(event.target.dataset.scrollTo);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user