Enable more oxlint rules (#34189)

* Fix type imports

* Fix jsdoc

* Fixup types

* Fix stray awaits on non-thenables

* Fixup imports

* Fix splats

* Fix this-context on callbacks

* Memoise react contexts

* Prefer find/flatMap

* Make oxlint happier about our React keys

* Avoid unsafe default function params

* Fixup jsdoc

* Fixup contexts

* Switch from eslint to oxlint

* Some oxlint-related tweaks

* Iterate

* Partial revert to defer some changes and shrink diff

* Iterate

* Add eslint-plugin-element-call and enable the copyright rule

* Set strictStorePkgContentCheck

* Iterate

* Enable forwardRef oxlint rule

* Enable no-unused-vars oxlint rule

* Enable no-implied-eval oxlint rule

* Enable no-duplicate-type-constituents oxlint rule

* Enable explicit-length-check oxlint rule

* Enable prefer-number-properties oxlint rule

* Enable no-callback-in-promise oxlint rule

* Enable no-require-imports oxlint rule

* Remove disablement of most unicorn oxlint rules

* Enable no-conditional-tests oxlint rule

* Enable promise-valid-params oxlint rule

* Enable require-unicode-regexp oxlint rule

* Remove max-len comments as we use oxfmt for formatting

* Enable majority of oxlint `suspicious` rules

* Iterate

* Fix oxlint type-aware lint running without dependencies built
This commit is contained in:
Michael Telatynski
2026-07-29 08:26:07 +00:00
committed by GitHub
parent e8848e4746
commit bbec915fb0
178 changed files with 405 additions and 403 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ if (process.env.VARIANT_PATH) {
console.log(`Using variant configuration from '${process.env.VARIANT_PATH}':`); console.log(`Using variant configuration from '${process.env.VARIANT_PATH}':`);
variant = { variant = {
...variant, ...variant,
...JSON.parse(fs.readFileSync(`${process.env.VARIANT_PATH}`, "utf8")), ...JSON.parse(fs.readFileSync(process.env.VARIANT_PATH, "utf8")),
}; };
} else { } else {
console.warn(`No VARIANT_PATH specified, using default variant configuration '${DEFAULT_VARIANT}':`); console.warn(`No VARIANT_PATH specified, using default variant configuration '${DEFAULT_VARIANT}':`);
+2 -1
View File
@@ -48,8 +48,9 @@ export default async function (hakEnv: HakEnv, moduleInfo: DependencyInfo): Prom
"` " + "` " +
"or your package manager if not using `rustup`", "or your package manager if not using `rustup`",
); );
return;
} }
fsProm.unlink("tmp").then(resolve); resolve(fsProm.unlink("tmp"));
}, },
); );
rustc.stdin!.write("fn main() {}"); rustc.stdin!.write("fn main() {}");
+1 -1
View File
@@ -27,7 +27,7 @@ export default async function copy(hakEnv: HakEnv, moduleInfo: DependencyInfo):
if (moduleInfo.moduleBuildDirs.length > 1) { if (moduleInfo.moduleBuildDirs.length > 1) {
if (!hakEnv.isMac()) { if (!hakEnv.isMac()) {
console.error( console.error(
"You asked me to copy multiple targets but I've only been taught " + "how to do that on macOS.", "You asked me to copy multiple targets but I've only been taught how to do that on macOS.",
); );
throw new Error("Can't copy multiple targets on this platform"); throw new Error("Can't copy multiple targets on this platform");
} }
+1 -1
View File
@@ -131,7 +131,7 @@ async function main(): Promise<void> {
for (const mod of modules) { for (const mod of modules) {
const depInfo = deps[mod]; const depInfo = deps[mod];
if (depInfo === undefined) { if (depInfo === undefined) {
console.log("Module " + mod + " not found - is it in hakDependencies " + "in your package.json?"); console.log(`Module ${mod} not found - is it in hakDependencies in your package.json?`);
process.exit(1); process.exit(1);
} }
console.log("hak " + cmd + ": " + mod); console.log("hak " + cmd + ": " + mod);
+6 -8
View File
@@ -42,12 +42,10 @@ describe("buildMenuTemplate", () => {
({ buildMenuTemplate } = await import("./vectormenu.js")); ({ buildMenuTemplate } = await import("./vectormenu.js"));
}); });
if (platform === "darwin") { it.runIf(platform === "darwin")("should have an app-named item first", () => {
it("should have an app-named item first", () => { const menu = buildMenuTemplate();
const menu = buildMenuTemplate(); expect(menu.items[0].label).toBe("ChatApp");
expect(menu.items[0].label).toBe("ChatApp"); });
});
}
it("should include expected `help` menu", () => { it("should include expected `help` menu", () => {
const menu = buildMenuTemplate(); const menu = buildMenuTemplate();
@@ -55,8 +53,8 @@ describe("buildMenuTemplate", () => {
const helpMenu = menu.items.at(-1)!; const helpMenu = menu.items.at(-1)!;
expect(helpMenu.label).toBe("common|help"); expect(helpMenu.label).toBe("common|help");
const helpSubmenu = helpMenu.submenu as unknown as MenuItemConstructorOptions[]; const helpSubmenu = helpMenu.submenu as unknown as MenuItemConstructorOptions[];
expect(helpSubmenu[0]!.label).toBe("common|brand_help"); expect(helpSubmenu[0].label).toBe("common|brand_help");
helpSubmenu[0]!.click!(menu.items.at(-1)!, undefined, new Event("click") as KeyboardEvent); helpSubmenu[0].click!(menu.items.at(-1)!, undefined, new Event("click") as KeyboardEvent);
expect(shell.openExternal).toHaveBeenCalledWith("https://i.need.help"); expect(shell.openExternal).toHaveBeenCalledWith("https://i.need.help");
}); });
}); });
+1
View File
@@ -13,6 +13,7 @@ declare module "webpack-version-file-plugin" {
extras?: Record<string, string>; extras?: Record<string, string>;
} }
// oxlint-disable-next-line typescript/no-extraneous-class
export default class VersionFilePlugin { export default class VersionFilePlugin {
public constructor(opts: Opts); public constructor(opts: Opts);
} }
@@ -609,7 +609,7 @@ class Helpers {
const roomListContainer = this.page.getByTestId("room-list"); const roomListContainer = this.page.getByTestId("room-list");
const roomTiles = roomListContainer.getByRole("option"); const roomTiles = roomListContainer.getByRole("option");
for (const [i, room] of rooms.entries()) { for (const [i, room] of rooms.entries()) {
await expect(roomTiles.nth(i)).toHaveAccessibleName(new RegExp(`${room.name}`)); await expect(roomTiles.nth(i)).toHaveAccessibleName(new RegExp(room.name));
} }
} }
} }
@@ -120,11 +120,11 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await sendUnthreadedReadReceipt(app, main2); await sendUnthreadedReadReceipt(app, main2);
// (So the room has no unreads) // (So the room has no unreads)
await expect(page.getByLabel(`${otherRoomName}`)).toBeVisible(); await expect(page.getByLabel(otherRoomName)).toBeVisible();
// And we persuade the app to persist its state to indexeddb by reloading and waiting // And we persuade the app to persist its state to indexeddb by reloading and waiting
await page.reload(); await page.reload();
await expect(page.getByLabel(`${selectedRoomName}`)).toBeVisible(); await expect(page.getByLabel(selectedRoomName)).toBeVisible();
// And we reload again, fetching the persisted state FROM indexeddb // And we reload again, fetching the persisted state FROM indexeddb
await page.reload(); await page.reload();
@@ -132,7 +132,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
// Then the room is read, because the persisted state correctly remembers both // Then the room is read, because the persisted state correctly remembers both
// receipts. (In #24629, the unthreaded receipt overwrote the main thread one, // receipts. (In #24629, the unthreaded receipt overwrote the main thread one,
// meaning that the room still said it had unread messages.) // meaning that the room still said it had unread messages.)
await expect(page.getByLabel(`${otherRoomName}`)).toBeVisible(); await expect(page.getByLabel(otherRoomName)).toBeVisible();
await expect(page.getByLabel(`${otherRoomName} Unread messages.`)).not.toBeVisible(); await expect(page.getByLabel(`${otherRoomName} Unread messages.`)).not.toBeVisible();
}); });
@@ -172,7 +172,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await sendThreadedReadReceipt(app, main3); await sendThreadedReadReceipt(app, main3);
// Then the room has no unreads // Then the room has no unreads
await expect(page.getByLabel(`${otherRoomName}`)).toBeVisible(); await expect(page.getByLabel(otherRoomName)).toBeVisible();
}); });
test("Recognises unread messages on other thread after receiving a receipt for earlier ones", async ({ test("Recognises unread messages on other thread after receiving a receipt for earlier ones", async ({
@@ -210,7 +210,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await sendThreadedReadReceipt(app, thread1b, main1); await sendThreadedReadReceipt(app, thread1b, main1);
// Then the room has no unreads // Then the room has no unreads
await expect(page.getByLabel(`${otherRoomName}`)).toBeVisible(); await expect(page.getByLabel(otherRoomName)).toBeVisible();
await util.goTo({ name: otherRoomName, roomId: otherRoomId }); await util.goTo({ name: otherRoomName, roomId: otherRoomId });
await util.assertReadThread("Message 1"); await util.assertReadThread("Message 1");
}); });
@@ -235,7 +235,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
// thread. The one in main is read because the unthreaded // thread. The one in main is read because the unthreaded
// receipt is for a later event. The room should therefore be // receipt is for a later event. The room should therefore be
// read, and the thread unread. // read, and the thread unread.
await expect(page.getByLabel(`${otherRoomName}`)).toBeVisible(); await expect(page.getByLabel(otherRoomName)).toBeVisible();
await util.goTo({ name: otherRoomName, roomId: otherRoomId }); await util.goTo({ name: otherRoomName, roomId: otherRoomId });
await util.assertUnreadThread("Message 1"); await util.assertUnreadThread("Message 1");
}); });
+4
View File
@@ -46,6 +46,10 @@
}, },
"dependsOn": ["^build", "^build:playwright"] "dependsOn": ["^build", "^build:playwright"]
}, },
"lint:prepare": {
"executor": "nx:noop",
"dependsOn": ["^build"]
},
"test:unit:prepare": { "test:unit:prepare": {
"executor": "nx:noop", "executor": "nx:noop",
"dependsOn": ["^build"] "dependsOn": ["^build"]
+1 -1
View File
@@ -459,7 +459,7 @@ export function bodyToNode(content: IContent, highlights?: string[], opts: Event
// This has to be done after the emojiBody check as to not break big emoji on replies // This has to be done after the emojiBody check as to not break big emoji on replies
formattedBody = formatEmojis(eventInfo.safeBody, true).join(""); formattedBody = formatEmojis(eventInfo.safeBody, true).join("");
} else { } else {
emojiBodyElements = formatEmojis(eventInfo.strippedBody, false) as JSX.Element[]; emojiBodyElements = formatEmojis(eventInfo.strippedBody, false);
} }
} }
+1 -1
View File
@@ -262,7 +262,7 @@ export default class Notifier extends TypedEventEmitter<keyof EmittedEvents, Emi
avatarUrl = Avatar.avatarUrlForMember(ev.sender, 40, 40, "crop"); avatarUrl = Avatar.avatarUrlForMember(ev.sender, 40, 40, "crop");
} }
const notif = plaf.displayNotification(title, msg!, avatarUrl, room, ev); const notif = plaf.displayNotification(title, msg, avatarUrl, room, ev);
// if displayNotification returns non-null, the platform supports // if displayNotification returns non-null, the platform supports
// clearing notifications later, so keep track of this. // clearing notifications later, so keep track of this.
+1
View File
@@ -11,6 +11,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import dis from "./dispatcher/dispatcher"; import dis from "./dispatcher/dispatcher";
// oxlint-disable-next-line typescript/no-extraneous-class
export default class Resend { export default class Resend {
public static resendUnsentEvents(room: Room): Promise<void[]> { public static resendUnsentEvents(room: Room): Promise<void[]> {
return Promise.all( return Promise.all(
+1 -1
View File
@@ -117,7 +117,7 @@ export function showAnyInviteErrors(
(avatarUrl && mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24)) ?? (avatarUrl && mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24)) ??
undefined undefined
} }
name={name!} name={name}
idName={user?.userId} idName={user?.userId}
size="36px" size="36px"
/> />
+1 -1
View File
@@ -237,7 +237,7 @@ function isRuleRoomMuteRuleForRoomId(roomId: string, rule: IPushRule): boolean {
return false; return false;
} }
// isRuleMaybeRoomMuteRule checks this condition exists // isRuleMaybeRoomMuteRule checks this condition exists
const cond = rule.conditions![0]!; const cond = rule.conditions![0];
return cond.pattern === roomId; return cond.pattern === roomId;
} }
+19 -19
View File
@@ -316,14 +316,14 @@ enum Action {
ReadEvents = "read_events", ReadEvents = "read_events",
} }
function sendResponse(event: MessageEvent<any>, res: any): void { function sendResponse(event: MessageEvent, res: any): void {
const data = objectClone(event.data); const data = objectClone(event.data);
data.response = res; data.response = res;
// @ts-ignore // @ts-ignore
event.source.postMessage(data, event.origin); event.source.postMessage(data, event.origin);
} }
function sendError(event: MessageEvent<any>, msg: string, nestedError?: Error): void { function sendError(event: MessageEvent, msg: string, nestedError?: Error): void {
logger.error("Action:" + event.data.action + " failed with message: " + msg); logger.error("Action:" + event.data.action + " failed with message: " + msg);
const data = objectClone(event.data); const data = objectClone(event.data);
data.response = { data.response = {
@@ -338,7 +338,7 @@ function sendError(event: MessageEvent<any>, msg: string, nestedError?: Error):
event.source.postMessage(data, event.origin); event.source.postMessage(data, event.origin);
} }
function inviteUser(event: MessageEvent<any>, roomId: string, userId: string): void { function inviteUser(event: MessageEvent, roomId: string, userId: string): void {
logger.log(`Received request to invite ${userId} into room ${roomId}`); logger.log(`Received request to invite ${userId} into room ${roomId}`);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
@@ -372,7 +372,7 @@ function inviteUser(event: MessageEvent<any>, roomId: string, userId: string): v
); );
} }
function kickUser(event: MessageEvent<any>, roomId: string, userId: string): void { function kickUser(event: MessageEvent, roomId: string, userId: string): void {
logger.log(`Received request to kick ${userId} from room ${roomId}`); logger.log(`Received request to kick ${userId} from room ${roomId}`);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
@@ -404,7 +404,7 @@ function kickUser(event: MessageEvent<any>, roomId: string, userId: string): voi
}); });
} }
function setWidget(event: MessageEvent<any>, roomId: string | null): void { function setWidget(event: MessageEvent, roomId: string | null): void {
const client = MatrixClientPeg.safeGet(); const client = MatrixClientPeg.safeGet();
const widgetId = event.data.widget_id; const widgetId = event.data.widget_id;
let widgetType = event.data.type; let widgetType = event.data.type;
@@ -488,7 +488,7 @@ function setWidget(event: MessageEvent<any>, roomId: string | null): void {
} }
} }
function getWidgets(event: MessageEvent<any>, roomId: string | null): void { function getWidgets(event: MessageEvent, roomId: string | null): void {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
sendError(event, _t("widget|error_need_to_be_logged_in")); sendError(event, _t("widget|error_need_to_be_logged_in"));
@@ -514,7 +514,7 @@ function getWidgets(event: MessageEvent<any>, roomId: string | null): void {
sendResponse(event, widgetStateEvents); sendResponse(event, widgetStateEvents);
} }
async function getRoomEncState(event: MessageEvent<any>, roomId: string): Promise<void> { async function getRoomEncState(event: MessageEvent, roomId: string): Promise<void> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
sendError(event, _t("widget|error_need_to_be_logged_in")); sendError(event, _t("widget|error_need_to_be_logged_in"));
@@ -530,7 +530,7 @@ async function getRoomEncState(event: MessageEvent<any>, roomId: string): Promis
sendResponse(event, roomIsEncrypted); sendResponse(event, roomIsEncrypted);
} }
function setPlumbingState(event: MessageEvent<any>, roomId: string, status: string): void { function setPlumbingState(event: MessageEvent, roomId: string, status: string): void {
if (typeof status !== "string") { if (typeof status !== "string") {
throw new Error("Plumbing state status should be a string"); throw new Error("Plumbing state status should be a string");
} }
@@ -552,7 +552,7 @@ function setPlumbingState(event: MessageEvent<any>, roomId: string, status: stri
); );
} }
function setBotOptions(event: MessageEvent<any>, roomId: string, userId: string): void { function setBotOptions(event: MessageEvent, roomId: string, userId: string): void {
logger.log(`Received request to set options for bot ${userId} in room ${roomId}`); logger.log(`Received request to set options for bot ${userId} in room ${roomId}`);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
@@ -572,7 +572,7 @@ function setBotOptions(event: MessageEvent<any>, roomId: string, userId: string)
} }
async function setBotPower( async function setBotPower(
event: MessageEvent<any>, event: MessageEvent,
roomId: string, roomId: string,
userId: string, userId: string,
level: number, level: number,
@@ -613,22 +613,22 @@ async function setBotPower(
} }
} }
function getMembershipState(event: MessageEvent<any>, roomId: string, userId: string): void { function getMembershipState(event: MessageEvent, roomId: string, userId: string): void {
logger.log(`membership_state of ${userId} in room ${roomId} requested.`); logger.log(`membership_state of ${userId} in room ${roomId} requested.`);
returnStateEvent(event, roomId, "m.room.member", userId); returnStateEvent(event, roomId, "m.room.member", userId);
} }
function getJoinRules(event: MessageEvent<any>, roomId: string): void { function getJoinRules(event: MessageEvent, roomId: string): void {
logger.log(`join_rules of ${roomId} requested.`); logger.log(`join_rules of ${roomId} requested.`);
returnStateEvent(event, roomId, "m.room.join_rules", ""); returnStateEvent(event, roomId, "m.room.join_rules", "");
} }
function botOptions(event: MessageEvent<any>, roomId: string, userId: string): void { function botOptions(event: MessageEvent, roomId: string, userId: string): void {
logger.log(`bot_options of ${userId} in room ${roomId} requested.`); logger.log(`bot_options of ${userId} in room ${roomId} requested.`);
returnStateEvent(event, roomId, "m.room.bot.options", "_" + userId); returnStateEvent(event, roomId, "m.room.bot.options", "_" + userId);
} }
function getMembershipCount(event: MessageEvent<any>, roomId: string): void { function getMembershipCount(event: MessageEvent, roomId: string): void {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
sendError(event, _t("widget|error_need_to_be_logged_in")); sendError(event, _t("widget|error_need_to_be_logged_in"));
@@ -643,7 +643,7 @@ function getMembershipCount(event: MessageEvent<any>, roomId: string): void {
sendResponse(event, count); sendResponse(event, count);
} }
function canSendEvent(event: MessageEvent<any>, roomId: string): void { function canSendEvent(event: MessageEvent, roomId: string): void {
const evType = "" + event.data.event_type; // force stringify const evType = "" + event.data.event_type; // force stringify
const isState = Boolean(event.data.is_state); const isState = Boolean(event.data.is_state);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
@@ -677,7 +677,7 @@ function canSendEvent(event: MessageEvent<any>, roomId: string): void {
sendResponse(event, true); sendResponse(event, true);
} }
function returnStateEvent(event: MessageEvent<any>, roomId: string, eventType: string, stateKey: string): void { function returnStateEvent(event: MessageEvent, roomId: string, eventType: string, stateKey: string): void {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
sendError(event, _t("widget|error_need_to_be_logged_in")); sendError(event, _t("widget|error_need_to_be_logged_in"));
@@ -696,7 +696,7 @@ function returnStateEvent(event: MessageEvent<any>, roomId: string, eventType: s
sendResponse(event, stateEvent.getContent()); sendResponse(event, stateEvent.getContent());
} }
async function getOpenIdToken(event: MessageEvent<any>): Promise<void> { async function getOpenIdToken(event: MessageEvent): Promise<void> {
try { try {
const tokenObject = await MatrixClientPeg.safeGet().getOpenIdToken(); const tokenObject = await MatrixClientPeg.safeGet().getOpenIdToken();
sendResponse(event, tokenObject); sendResponse(event, tokenObject);
@@ -841,7 +841,7 @@ async function readEvents(
} }
} }
const onMessage = function (event: MessageEvent<any>): void { const onMessage = function (event: MessageEvent): void {
if (!event.origin) { if (!event.origin) {
// @ts-ignore - stupid chrome // @ts-ignore - stupid chrome
event.origin = event.originalEvent.origin; event.origin = event.originalEvent.origin;
@@ -987,7 +987,7 @@ export function stopListening(): void {
} }
if (listenerCount < 0) { if (listenerCount < 0) {
// Make an error so we get a stack trace // Make an error so we get a stack trace
const e = new Error("ScalarMessaging: mismatched startListening / stopListening detected." + " Negative count"); const e = new Error("ScalarMessaging: mismatched startListening / stopListening detected. Negative count");
logger.error(e); logger.error(e);
} }
} }
+1
View File
@@ -95,6 +95,7 @@ type ObjectType<K extends keyof IConfigOptions> = IConfigOptions[K] extends obje
? SnakedObject<NonNullable<IConfigOptions[K]>> ? SnakedObject<NonNullable<IConfigOptions[K]>>
: SnakedObject<NonNullable<IConfigOptions[K]>> | null | undefined; : SnakedObject<NonNullable<IConfigOptions[K]>> | null | undefined;
// oxlint-disable-next-line typescript/no-extraneous-class
export default class SdkConfig { export default class SdkConfig {
private static instance: DeepReadonly<IConfigOptions>; private static instance: DeepReadonly<IConfigOptions>;
private static fallback: SnakedObject<DeepReadonly<IConfigOptions>>; private static fallback: SnakedObject<DeepReadonly<IConfigOptions>>;
+1 -1
View File
@@ -49,7 +49,7 @@ function getRoomMemberDisplayname(client: MatrixClient, event: MatrixEvent, user
} }
function textForCallEvent(event: MatrixEvent, client: MatrixClient): () => string { function textForCallEvent(event: MatrixEvent, client: MatrixClient): () => string {
const roomName = client.getRoom(event.getRoomId()!)?.name; const roomName = client.getRoom(event.getRoomId())?.name;
const isSupported = client.supportsVoip(); const isSupported = client.supportsVoip();
return isSupported return isSupported
@@ -96,7 +96,7 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
return true; return true;
}) })
.reduce((o, key) => { .reduce((o, key) => {
o[key as KeyBindingAction] = KEYBOARD_SHORTCUTS[key as KeyBindingAction]; o[key] = KEYBOARD_SHORTCUTS[key];
return o; return o;
}, {} as IKeyboardShortcuts); }, {} as IKeyboardShortcuts);
}; };
@@ -32,6 +32,7 @@ const ORDERED_LANDMARKS = [
* The landmarks are cycled through in the following order: * The landmarks are cycled through in the following order:
* ACTIVE_SPACE_BUTTON <-> ROOM_SEARCH <-> ROOM_LIST <-> MESSAGE_COMPOSER/HOME <-> ACTIVE_SPACE_BUTTON * ACTIVE_SPACE_BUTTON <-> ROOM_SEARCH <-> ROOM_LIST <-> MESSAGE_COMPOSER/HOME <-> ACTIVE_SPACE_BUTTON
*/ */
// oxlint-disable-next-line typescript/no-extraneous-class
export class LandmarkNavigation { export class LandmarkNavigation {
/** /**
* Get the next/previous landmark that must be focused from a given landmark * Get the next/previous landmark that must be focused from a given landmark
+1
View File
@@ -18,6 +18,7 @@ import { type AsyncActionPayload } from "../dispatcher/payloads";
import { DefaultTagID, type TagID } from "../stores/room-list-v3/skip-list/tag"; import { DefaultTagID, type TagID } from "../stores/room-list-v3/skip-list/tag";
import ErrorDialog from "../components/views/dialogs/ErrorDialog"; import ErrorDialog from "../components/views/dialogs/ErrorDialog";
// oxlint-disable-next-line typescript/no-extraneous-class
export default class RoomListActions { export default class RoomListActions {
/** /**
* Creates an action thunk that will do an asynchronous request to * Creates an action thunk that will do an asynchronous request to
+3 -3
View File
@@ -62,16 +62,16 @@ class MxVoiceWorklet extends AudioWorkletProcessor {
const maxVal = Math.max(...monoChan); const maxVal = Math.max(...monoChan);
const amplitude = percentageOf(maxVal, -1, 1) - percentageOf(minVal, -1, 1); const amplitude = percentageOf(maxVal, -1, 1) - percentageOf(minVal, -1, 1);
this.port.postMessage(<IAmplitudePayload>{ this.port.postMessage({
ev: PayloadEvent.AmplitudeMark, ev: PayloadEvent.AmplitudeMark,
amplitude: amplitude, amplitude: amplitude,
forIndex: this.amplitudeIndex++, forIndex: this.amplitudeIndex++,
}); } satisfies IAmplitudePayload);
this.nextAmplitudeSecond = nextTimeForTargetFreq(currentSecond); this.nextAmplitudeSecond = nextTimeForTargetFreq(currentSecond);
} }
// We mostly use this worklet to fire regular clock updates through to components // We mostly use this worklet to fire regular clock updates through to components
this.port.postMessage(<ITimingPayload>{ ev: PayloadEvent.Timekeep, timeSeconds: currentTime }); this.port.postMessage({ ev: PayloadEvent.Timekeep, timeSeconds: currentTime } satisfies ITimingPayload);
// We're supposed to return false when we're "done" with the audio clip, but seeing as // We're supposed to return false when we're "done" with the audio clip, but seeing as
// we are acting as a passive processor we are never truly "done". The browser will clean // we are acting as a passive processor we are never truly "done". The browser will clean
+1 -1
View File
@@ -40,7 +40,7 @@ export default class NotifProvider extends AutocompleteProvider {
if ( if (
command?.[0] && command?.[0] &&
command[0].length > 1 && command[0].length > 1 &&
["@room", "@channel", "@everyone", "@here"].some((c) => c.startsWith(command![0])) ["@room", "@channel", "@everyone", "@here"].some((c) => c.startsWith(command[0]))
) { ) {
return [ return [
{ {
@@ -228,7 +228,7 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil
} }
// When an <input> is focused, only handle the Escape key // When an <input> is focused, only handle the Escape key
if (checkInputableElement(ev.target as HTMLElement) && action !== KeyBindingAction.Escape) { if (checkInputableElement(ev.target) && action !== KeyBindingAction.Escape) {
return; return;
} }
@@ -123,7 +123,7 @@ export function GenericDropdownMenu<T>({
className, className,
AdditionalOptions, AdditionalOptions,
}: IProps<T>): JSX.Element { }: IProps<T>): JSX.Element {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu<HTMLElement>(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
const valueKey = calculateKey(value, toKey); const valueKey = calculateKey(value, toKey);
const selected: GenericDropdownMenuItem<T> | undefined = options const selected: GenericDropdownMenuItem<T> | undefined = options
@@ -1264,7 +1264,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
if (isOnlyAdmin(roomToLeave)) { if (isOnlyAdmin(roomToLeave)) {
const userLevelValues = roomToLeave.getJoinedMembers().map((m) => m.powerLevel); const userLevelValues = roomToLeave.getJoinedMembers().map((m) => m.powerLevel);
const maxUserLevel = Math.max(...(userLevelValues as number[])); const maxUserLevel = Math.max(...userLevelValues);
const warning = const warning =
maxUserLevel >= 100 maxUserLevel >= 100
@@ -31,7 +31,7 @@ export type CreatePipChildren = (options: IChildrenOptions) => JSX.Element;
interface IChildrenOptions { interface IChildrenOptions {
// a callback which is called when a mouse event (most likely mouse down) occurs at start of moving the pip around // a callback which is called when a mouse event (most likely mouse down) occurs at start of moving the pip around
onStartMoving: (event: React.MouseEvent<Element, MouseEvent>) => void; onStartMoving: (event: React.MouseEvent) => void;
// a callback which is called when the content fo the pip changes in a way that is likely to cause a resize // a callback which is called when the content fo the pip changes in a way that is likely to cause a resize
onResize: (event: Event) => void; onResize: (event: Event) => void;
} }
@@ -256,7 +256,7 @@ export default class RightPanel extends React.Component<Props, IState> {
<RoomSummaryCardView <RoomSummaryCardView
room={this.props.room} room={this.props.room}
// whenever RightPanel is passed a room it is passed a permalinkcreator // whenever RightPanel is passed a room it is passed a permalinkcreator
permalinkCreator={this.props.permalinkCreator!} permalinkCreator={this.props.permalinkCreator}
onSearchChange={this.props.onSearchChange} onSearchChange={this.props.onSearchChange}
onSearchCancel={this.props.onSearchCancel} onSearchCancel={this.props.onSearchCancel}
searchTerm={this.props.searchTerm} searchTerm={this.props.searchTerm}
@@ -1523,8 +1523,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.setState({ membersLoaded: true }); this.setState({ membersLoaded: true });
} }
} catch (err) { } catch (err) {
const errorMessage = const errorMessage = `Fetching room members for ${room.roomId} failed. Room members will appear incomplete.`;
`Fetching room members for ${room.roomId} failed.` + " Room members will appear incomplete.";
logger.error(errorMessage); logger.error(errorMessage);
logger.error(err); logger.error(err);
} }
@@ -457,7 +457,7 @@ export default class ScrollPanel extends React.Component<IProps> {
this.unfillDebouncer = window.setTimeout(() => { this.unfillDebouncer = window.setTimeout(() => {
this.unfillDebouncer = null; this.unfillDebouncer = null;
debuglog("unfilling now", { backwards, origExcessHeight }); debuglog("unfilling now", { backwards, origExcessHeight });
this.props.onUnfillRequest?.(backwards, markerScrollToken!); this.props.onUnfillRequest?.(backwards, markerScrollToken);
}, UNFILL_REQUEST_DEBOUNCE_MS); }, UNFILL_REQUEST_DEBOUNCE_MS);
} }
} }
@@ -666,7 +666,7 @@ export default class ScrollPanel extends React.Component<IProps> {
debuglog("unable to save scroll state: found no children in the viewport"); debuglog("unable to save scroll state: found no children in the viewport");
return; return;
} }
const scrollToken = node!.dataset.scrollTokens?.split(",")[0]; const scrollToken = node.dataset.scrollTokens?.split(",")[0];
debuglog("saving anchored scroll state to message", scrollToken); debuglog("saving anchored scroll state to message", scrollToken);
const bottomOffset = this.topFromBottom(node); const bottomOffset = this.topFromBottom(node);
this.scrollState = { this.scrollState = {
@@ -788,7 +788,7 @@ export default class ScrollPanel extends React.Component<IProps> {
const m = messages[i] as HTMLElement; const m = messages[i] as HTMLElement;
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens // 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
// There might only be one scroll token // There might only be one scroll token
if (scrollToken && m.dataset.scrollTokens?.split(",").includes(scrollToken!)) { if (scrollToken && m.dataset.scrollTokens?.split(",").includes(scrollToken)) {
node = m; node = m;
break; break;
} }
@@ -62,7 +62,6 @@ import { Action } from "../../dispatcher/actions";
import { type IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex"; import { type IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter"; import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
import { type IOOBData } from "../../stores/ThreepidInviteStore";
import { awaitRoomDownSync } from "../../utils/RoomUpgrade"; import { awaitRoomDownSync } from "../../utils/RoomUpgrade";
import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
import { type JoinRoomReadyPayload } from "../../dispatcher/payloads/JoinRoomReadyPayload"; import { type JoinRoomReadyPayload } from "../../dispatcher/payloads/JoinRoomReadyPayload";
@@ -400,7 +399,7 @@ export const showRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: st
// XXX: This logic is duplicated from the JS SDK which would normally decide what the name is. // XXX: This logic is duplicated from the JS SDK which would normally decide what the name is.
name: room?.name || roomAlias || _t("common|unnamed_room"), name: room?.name || roomAlias || _t("common|unnamed_room"),
roomType, roomType,
} as IOOBData, },
metricsTrigger: "RoomDirectory", metricsTrigger: "RoomDirectory",
}); });
}; };
@@ -617,7 +617,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
if (showSetup) { if (showSetup) {
phase = phase =
this.props.justCreatedOpts!.createOpts?.preset === Preset.PublicChat this.props.justCreatedOpts.createOpts?.preset === Preset.PublicChat
? Phase.PublicCreateRooms ? Phase.PublicCreateRooms
: Phase.PrivateScope; : Phase.PrivateScope;
} }
@@ -70,7 +70,7 @@ export const ThreadPanelHeader: React.FC<{
}> = ({ filterOption, setFilterOption }) => { }> = ({ filterOption, setFilterOption }) => {
const mxClient = useMatrixClientContext(); const mxClient = useMatrixClientContext();
const roomContext = useScopedRoomContext("room"); const roomContext = useScopedRoomContext("room");
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu<HTMLElement>(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
const options: readonly ThreadPanelHeaderOption[] = [ const options: readonly ThreadPanelHeaderOption[] = [
{ {
label: _t("threads|all_threads"), label: _t("threads|all_threads"),
@@ -272,7 +272,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
this.timelinePanel.current?.refreshTimeline(this.props.initialEvent?.getId()); this.timelinePanel.current?.refreshTimeline(this.props.initialEvent?.getId());
} }
private setupThreadListeners(thread?: Thread | undefined, oldThread?: Thread | undefined): void { private setupThreadListeners(thread?: Thread, oldThread?: Thread): void {
if (oldThread) { if (oldThread) {
this.state.thread?.off(ThreadEvent.NewReply, this.updateThreadRelation); this.state.thread?.off(ThreadEvent.NewReply, this.updateThreadRelation);
this.props.room.off(RoomEvent.LocalEchoUpdated, this.updateThreadRelation); this.props.room.off(RoomEvent.LocalEchoUpdated, this.updateThreadRelation);
@@ -1532,6 +1532,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
description, description,
}); });
if (onFinished) { if (onFinished) {
// oxlint-disable-next-line promise/no-promise-in-callback
finished.then(onFinished); finished.then(onFinished);
} }
}; };
@@ -63,7 +63,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
public componentWillUnmount(): void { public componentWillUnmount(): void {
this.unmounted = true; this.unmounted = true;
dis.unregister(this.dispatcherRef!); dis.unregister(this.dispatcherRef);
} }
private getUploadsInRoom(): RoomUpload[] { private getUploadsInRoom(): RoomUpload[] {
@@ -339,7 +339,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
homeserver={this.props.serverConfig.hsName} homeserver={this.props.serverConfig.hsName}
loading={this.state.phase === Phase.SendingEmail} loading={this.state.phase === Phase.SendingEmail}
onInputChanged={this.onInputChanged} onInputChanged={this.onInputChanged}
onLoginClick={this.props.onLoginClick!} // set by default props onLoginClick={this.props.onLoginClick} // set by default props
onSubmitForm={this.onSubmitForm} onSubmitForm={this.onSubmitForm}
/> />
); );
@@ -89,7 +89,7 @@ export default class LegacySeekBar extends React.PureComponent<IProps, IState> {
this.props.playback.skipTo(Number(ev.target.value) * this.props.playback.durationSeconds); this.props.playback.skipTo(Number(ev.target.value) * this.props.playback.durationSeconds);
}; };
private onMouseDown = (event: React.MouseEvent<Element, MouseEvent>): void => { private onMouseDown = (event: React.MouseEvent): void => {
// do not propagate mouse down events, because these should be handled by the seekbar // do not propagate mouse down events, because these should be handled by the seekbar
event.stopPropagation(); event.stopPropagation();
}; };
@@ -286,9 +286,7 @@ export default class LoginWithQR extends React.Component<Props, IState> {
} catch (e: RendezvousError | unknown) { } catch (e: RendezvousError | unknown) {
if (abortController.signal.aborted) return; if (abortController.signal.aborted) return;
logger.error("Error whilst approving login", e); logger.error("Error whilst approving login", e);
await rendezvous.cancel( await rendezvous.cancel(e instanceof RendezvousError ? e.code : ClientRendezvousFailureReason.Unknown);
e instanceof RendezvousError ? (e.code as MSC4108FailureReason) : ClientRendezvousFailureReason.Unknown,
);
} }
}; };
@@ -205,7 +205,7 @@ export default class BugReportDialog extends React.Component<BugReportDialogProp
this.setState({ this.setState({
downloadBusy: false, downloadBusy: false,
downloadProgress: downloadProgress:
_t("bug_reporting|failed_download_logs") + `${err instanceof Error ? err.message : ""}`, _t("bug_reporting|failed_download_logs") + (err instanceof Error ? err.message : ""),
}); });
} }
} }
@@ -108,7 +108,7 @@ export default class ChangelogDialog extends React.Component<IProps, State> {
msg: this.state[repo], msg: this.state[repo],
}); });
} else { } else {
content = (this.state[repo] as Commit[]).map(this.elementsForCommit); content = this.state[repo].map(this.elementsForCommit);
} }
return ( return (
<div key={repo}> <div key={repo}>
@@ -381,7 +381,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
className="mx_ExportDialog_attachments-checkbox" className="mx_ExportDialog_attachments-checkbox"
id="include-attachments" id="include-attachments"
checked={includeAttachments} checked={includeAttachments}
onChange={(e) => setAttachments((e.target as HTMLInputElement).checked)} onChange={(e) => setAttachments(e.target.checked)}
> >
{_t("export_chat|include_attachments")} {_t("export_chat|include_attachments")}
</StyledCheckbox> </StyledCheckbox>
@@ -78,10 +78,7 @@ const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
}} }}
autoFocus={true} autoFocus={true}
/> />
<StyledCheckbox <StyledCheckbox checked={canContact} onChange={(e) => setCanContact(e.target.checked)}>
checked={canContact}
onChange={(e) => setCanContact((e.target as HTMLInputElement).checked)}
>
{_t("feedback|can_contact_label")} {_t("feedback|can_contact_label")}
</StyledCheckbox> </StyledCheckbox>
</React.Fragment> </React.Fragment>
@@ -323,10 +323,10 @@ describe("InviteDialog", () => {
const input = screen.getByTestId("invite-dialog-input"); const input = screen.getByTestId("invite-dialog-input");
input.focus(); input.focus();
await userEvent.paste(`${bobbob}`); await userEvent.paste(bobbob);
await screen.findAllByText(bobId); await screen.findAllByText(bobId);
expect(input).toHaveValue(`${bobbob}`); expect(input).toHaveValue(bobbob);
}); });
it("should allow to invite multiple emails to a room", async () => { it("should allow to invite multiple emails to a room", async () => {
@@ -409,9 +409,9 @@ describe("InviteDialog", () => {
const input = screen.getByTestId("invite-dialog-input"); const input = screen.getByTestId("invite-dialog-input");
input.focus(); input.focus();
await userEvent.paste(`${bobId}`); await userEvent.paste(bobId);
await userEvent.paste(`${bobId}`); await userEvent.paste(bobId);
await userEvent.paste(`${bobId}`); await userEvent.paste(bobId);
expect(input).toHaveValue(""); expect(input).toHaveValue("");
await expect(screen.findAllByText(bobId, { selector: "a" })).resolves.toHaveLength(1); await expect(screen.findAllByText(bobId, { selector: "a" })).resolves.toHaveLength(1);
@@ -422,7 +422,7 @@ describe("InviteDialog", () => {
const input = screen.getByTestId("invite-dialog-input"); const input = screen.getByTestId("invite-dialog-input");
input.focus(); input.focus();
await userEvent.keyboard(`${aliceId}`); await userEvent.keyboard(aliceId);
const btn = await screen.findByRole("option", { name: aliceId }); const btn = await screen.findByRole("option", { name: aliceId });
fireEvent.click(btn); fireEvent.click(btn);
@@ -116,7 +116,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
if (isClose || !this.possibleButtons.includes(ev.detail.data.button)) { if (isClose || !this.possibleButtons.includes(ev.detail.data.button)) {
return this.state.messaging?.transport.reply(ev.detail, { return this.state.messaging?.transport.reply(ev.detail, {
error: { message: "Invalid button" }, error: { message: "Invalid button" },
} as IWidgetApiErrorResponseData); } satisfies IWidgetApiErrorResponseData);
} }
let buttonIds: ModalButtonID[]; let buttonIds: ModalButtonID[];
@@ -129,7 +129,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
buttonIds = Array.from(tempSet); buttonIds = Array.from(tempSet);
} }
this.setState({ disabledButtonIds: buttonIds }); this.setState({ disabledButtonIds: buttonIds });
this.state.messaging?.transport.reply(ev.detail, {} as IWidgetApiAcknowledgeResponseData); this.state.messaging?.transport.reply(ev.detail, {} satisfies IWidgetApiAcknowledgeResponseData);
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
@@ -207,6 +207,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
<iframe <iframe
title={this.widget.name ?? undefined} title={this.widget.name ?? undefined}
ref={this.appFrame} ref={this.appFrame}
// oxlint-disable-next-line react/iframe-missing-sandbox
sandbox="allow-forms allow-scripts allow-same-origin" sandbox="allow-forms allow-scripts allow-same-origin"
src={widgetUrl} src={widgetUrl}
onLoad={this.onLoad} onLoad={this.onLoad}
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { createRef, type RefObject } from "react"; import React, { createRef } from "react";
import { type DialogContent, type DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent"; import { type DialogContent, type DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { type ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi"; import { type ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
@@ -75,10 +75,6 @@ export class ModuleUiDialog<
} as unknown as P; } as unknown as P;
// XXX: we have to fudge the types here a little as the react-sdk-module-api lacks React 19 support // XXX: we have to fudge the types here a little as the react-sdk-module-api lacks React 19 support
return ( return <div className="mx_ModuleUiDialog">{this.props.contentFactory(contentProps, this.contentRef)}</div>;
<div className="mx_ModuleUiDialog">
{this.props.contentFactory(contentProps, this.contentRef as RefObject<C>)}
</div>
);
} }
} }
@@ -41,7 +41,7 @@ export class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
<BaseAvatar <BaseAvatar
url={ url={
this.props.member.getMxcAvatarUrl() this.props.member.getMxcAvatarUrl()
? mediaFromMxc(this.props.member.getMxcAvatarUrl()!).getSquareThumbnailHttp( ? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(
parseInt(avatarSize, 10), parseInt(avatarSize, 10),
) )
: null : null
@@ -20,7 +20,7 @@ import { Tooltip } from "@vector-im/compound-web";
import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Element> | React.FormEvent<Element>; export type ButtonEvent = React.MouseEvent | React.KeyboardEvent | React.FormEvent;
/** /**
* The kind of button, similar to how Bootstrap works. * The kind of button, similar to how Bootstrap works.
@@ -306,8 +306,8 @@ export default class EventListSummary extends React.Component<Props, State> {
let transition = t; let transition = t;
if (i < transitions.length - 1 && modMap[t] && modMap[t]!.after === t2) { if (i < transitions.length - 1 && modMap[t] && modMap[t].after === t2) {
transition = modMap[t]!.newTransition; transition = modMap[t].newTransition;
i++; i++;
} }
@@ -459,7 +459,7 @@ export default class ImageView extends React.Component<IProps, IState> {
let info: JSX.Element | undefined; let info: JSX.Element | undefined;
if (showEventMeta) { if (showEventMeta) {
const mxEvent = this.props.mxEvent!; const mxEvent = this.props.mxEvent;
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
let permalink = "#"; let permalink = "#";
if (this.props.permalinkCreator) { if (this.props.permalinkCreator) {
@@ -163,7 +163,7 @@ const MapComponent: React.FC<MapProps> = ({
}) => { }) => {
const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive, bounds, allowGeolocate }); const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive, bounds, allowGeolocate });
const onMapClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>): void => { const onMapClick = (event: React.MouseEvent<HTMLDivElement>): void => {
// Eat click events when clicking the attribution button // Eat click events when clicking the attribution button
const target = event.target as Element; const target = event.target as Element;
if (target.classList.contains("maplibregl-ctrl-attrib-button")) { if (target.classList.contains("maplibregl-ctrl-attrib-button")) {
@@ -39,7 +39,7 @@ const OptionalTooltip: React.FC<{
const show = (): void => setIsVisible(true); const show = (): void => setIsVisible(true);
const hide = (): void => setIsVisible(false); const hide = (): void => setIsVisible(false);
const toggleVisibility = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => { const toggleVisibility = (e: React.MouseEvent<HTMLDivElement>): void => {
// stop map from zooming in on click // stop map from zooming in on click
e.stopPropagation(); e.stopPropagation();
setIsVisible(!isVisible); setIsVisible(!isVisible);
@@ -25,7 +25,7 @@ import { type Media, mediaFromContent } from "../../../customisations/Media";
import { BLURHASH_FIELD, createThumbnail } from "../../../utils/image-media"; import { BLURHASH_FIELD, createThumbnail } from "../../../utils/image-media";
import ImageView from "../elements/ImageView"; import ImageView from "../elements/ImageView";
import { type IBodyProps } from "./IBodyProps"; import { type IBodyProps } from "./IBodyProps";
import { type ImageSize, suggestedSize as suggestedImageSize } from "../../../settings/enums/ImageSize"; import { suggestedSize as suggestedImageSize } from "../../../settings/enums/ImageSize";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import { blobIsAnimated, mayBeAnimated } from "../../../utils/Image"; import { blobIsAnimated, mayBeAnimated } from "../../../utils/Image";
@@ -409,7 +409,7 @@ export class ImageBodyBaseInner extends React.Component<ImageBodyBaseProps, ISta
} }
const { w: maxWidth, h: maxHeight } = suggestedImageSize( const { w: maxWidth, h: maxHeight } = suggestedImageSize(
SettingsStore.getValue("Images.size") as ImageSize, SettingsStore.getValue("Images.size"),
{ w: infoWidth, h: infoHeight }, { w: infoWidth, h: infoHeight },
forcedHeight ?? this.props.maxImageHeight, forcedHeight ?? this.props.maxImageHeight,
); );
@@ -409,8 +409,8 @@ export function allVotes(voteRelations: Relations): Array<UserVote> {
*/ */
export function collectUserVotes( export function collectUserVotes(
userResponses: Array<UserVote>, userResponses: Array<UserVote>,
userId?: string | null | undefined, userId?: string | null,
selected?: string | null | undefined, selected?: string | null,
): Map<string, UserVote> { ): Map<string, UserVote> {
const userVotes: Map<string, UserVote> = new Map(); const userVotes: Map<string, UserVote> = new Map();
@@ -129,13 +129,13 @@ export const PollHistoryList: React.FC<PollHistoryListProps> = ({
{pollStartEvents.map((pollStartEvent) => {pollStartEvents.map((pollStartEvent) =>
filter === "ACTIVE" ? ( filter === "ACTIVE" ? (
<PollListItem <PollListItem
key={pollStartEvent.getId()!} key={pollStartEvent.getId()}
event={pollStartEvent} event={pollStartEvent}
onClick={() => onItemClick(pollStartEvent.getId()!)} onClick={() => onItemClick(pollStartEvent.getId()!)}
/> />
) : ( ) : (
<PollListItemEnded <PollListItemEnded
key={pollStartEvent.getId()!} key={pollStartEvent.getId()}
event={pollStartEvent} event={pollStartEvent}
poll={polls.get(pollStartEvent.getId()!)!} poll={polls.get(pollStartEvent.getId()!)!}
onClick={() => onItemClick(pollStartEvent.getId()!)} onClick={() => onItemClick(pollStartEvent.getId()!)}
@@ -195,14 +195,14 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
let content: JSX.Element | undefined; let content: JSX.Element | undefined;
switch (phase) { switch (phase) {
case RightPanelPhases.MemberInfo: case RightPanelPhases.MemberInfo:
content = <UserInfoBasicView room={room as Room} member={member as User} />; content = <UserInfoBasicView room={room as Room} member={member} />;
break; break;
case RightPanelPhases.EncryptionPanel: case RightPanelPhases.EncryptionPanel:
classes.push("mx_UserInfo_smallAvatar"); classes.push("mx_UserInfo_smallAvatar");
content = ( content = (
<EncryptionPanel <EncryptionPanel
{...(props as React.ComponentProps<typeof EncryptionPanel>)} {...(props as React.ComponentProps<typeof EncryptionPanel>)}
member={member as User | RoomMember} member={member}
onClose={onEncryptionPanelClose} onClose={onEncryptionPanelClose}
isRoomEncrypted={Boolean(isRoomEncrypted)} isRoomEncrypted={Boolean(isRoomEncrypted)}
/> />
@@ -240,11 +240,11 @@ export default class AliasSettings extends React.Component<IProps, IState> {
.createAlias(alias, this.props.roomId) .createAlias(alias, this.props.roomId)
.then(() => { .then(() => {
this.setState({ this.setState({
localAliases: this.state.localAliases.concat(alias!), localAliases: this.state.localAliases.concat(alias),
newAlias: undefined, newAlias: undefined,
}); });
if (!this.state.canonicalAlias) { if (!this.state.canonicalAlias) {
this.changeCanonicalAlias(alias!); this.changeCanonicalAlias(alias);
} }
}) })
.catch((err) => { .catch((err) => {
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type AriaRole, useContext } from "react"; import React, { type AriaRole, useContext } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { Resizable, type Size } from "re-resizable"; import { Resizable } from "re-resizable";
import { type Room } from "matrix-js-sdk/src/matrix"; import { type Room } from "matrix-js-sdk/src/matrix";
import { type IWidget } from "matrix-widget-api"; import { type IWidget } from "matrix-widget-api";
import { clamp, percentageOf, percentageWithin } from "@element-hq/web-shared-components"; import { clamp, percentageOf, percentageWithin } from "@element-hq/web-shared-components";
@@ -340,7 +340,7 @@ const PersistentVResizer: React.FC<IPersistentResizerProps> = ({
<Resizable <Resizable
// types do not support undefined height/width // types do not support undefined height/width
// but resizable code checks specifically for undefined on Size prop // but resizable code checks specifically for undefined on Size prop
size={{ height: Math.min(defaultHeight, maxHeight), width: undefined } as unknown as Size} size={{ height: Math.min(defaultHeight, maxHeight), width: undefined }}
minHeight={minHeight} minHeight={minHeight}
maxHeight={maxHeight} maxHeight={maxHeight}
onResizeStart={() => { onResizeStart={() => {
@@ -350,7 +350,7 @@ const PersistentVResizer: React.FC<IPersistentResizerProps> = ({
resizeNotifier.notifyTimelineHeightChanged(); resizeNotifier.notifyTimelineHeightChanged();
}} }}
onResizeStop={(e, dir, ref, d) => { onResizeStop={(e, dir, ref, d) => {
let newHeight = defaultHeight! + d.height; let newHeight = defaultHeight + d.height;
newHeight = percentageOf(newHeight, minHeight, maxHeight) * 100; newHeight = percentageOf(newHeight, minHeight, maxHeight) * 100;
sdkContext.widgetLayoutStore.setContainerHeight(room, "top", newHeight); sdkContext.widgetLayoutStore.setContainerHeight(room, "top", newHeight);
@@ -668,7 +668,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
private readonly onFocusWithin = (event: FocusEvent<HTMLElement>): void => { private readonly onFocusWithin = (event: FocusEvent<HTMLElement>): void => {
// Show the action toolbar for keyboard-visible focus, with what-input as a fallback signal. // Show the action toolbar for keyboard-visible focus, with what-input as a fallback signal.
const target = event.target as HTMLElement; const target = event.target;
const showActionBarFromFocus = const showActionBarFromFocus =
target.matches(":focus-visible") || document.body.dataset["data-whatinput"] === "keyboard"; target.matches(":focus-visible") || document.body.dataset["data-whatinput"] === "keyboard";
this.setState((prevState) => ({ this.setState((prevState) => ({
@@ -179,7 +179,7 @@ const NewRoomIntro: React.FC = () => {
let parentSpace: Room | undefined; let parentSpace: Room | undefined;
if ( if (
sdkContext.spaceStore.activeSpaceRoom?.canInvite(cli.getSafeUserId()) && sdkContext.spaceStore.activeSpaceRoom?.canInvite(cli.getSafeUserId()) &&
sdkContext.spaceStore.isRoomInSpace(sdkContext.spaceStore.activeSpace!, room.roomId) sdkContext.spaceStore.isRoomInSpace(sdkContext.spaceStore.activeSpace, room.roomId)
) { ) {
parentSpace = sdkContext.spaceStore.activeSpaceRoom; parentSpace = sdkContext.spaceStore.activeSpaceRoom;
} }
@@ -192,7 +192,7 @@ const NewRoomIntro: React.FC = () => {
className="mx_NewRoomIntro_inviteButton" className="mx_NewRoomIntro_inviteButton"
kind="primary" kind="primary"
onClick={() => { onClick={() => {
showSpaceInvite(parentSpace!); showSpaceInvite(parentSpace);
}} }}
> >
<UserAddIcon /> <UserAddIcon />
@@ -38,7 +38,7 @@ export const RoomListPanel: React.FC<RoomListPanelProps> = ({ activeSpace }) =>
const [focusedElement, setFocusedElement] = useState<Element | null>(null); const [focusedElement, setFocusedElement] = useState<Element | null>(null);
const onFocus = useCallback((ev: React.FocusEvent): void => { const onFocus = useCallback((ev: React.FocusEvent): void => {
setFocusedElement(ev.target as Element); setFocusedElement(ev.target);
}, []); }, []);
const onBlur = useCallback((): void => { const onBlur = useCallback((): void => {
@@ -218,7 +218,7 @@ class RoomPreviewBar extends React.Component<IProps, IState> {
} }
return MessageCase.Invite; return MessageCase.Invite;
} else if (this.props.error) { } else if (this.props.error) {
if ((this.props.error as MatrixError).errcode == "M_NOT_FOUND") { if (this.props.error.errcode === "M_NOT_FOUND") {
return MessageCase.RoomNotFound; return MessageCase.RoomNotFound;
} else { } else {
return MessageCase.OtherError; return MessageCase.OtherError;
@@ -610,7 +610,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
// Fallback to internal onPaste handler // Fallback to internal onPaste handler
return false; return false;
} }
const imgSrc = imgDoc!.querySelector("img")!.src; const imgSrc = imgDoc.querySelector("img")!.src;
fetch(imgSrc).then( fetch(imgSrc).then(
(response) => { (response) => {
@@ -66,7 +66,7 @@ export default class WhoIsTypingTile extends React.Component<IProps, IState> {
client.removeListener(RoomMemberEvent.Typing, this.onRoomMemberTyping); client.removeListener(RoomMemberEvent.Typing, this.onRoomMemberTyping);
client.removeListener(RoomEvent.Timeline, this.onRoomTimeline); client.removeListener(RoomEvent.Timeline, this.onRoomTimeline);
} }
Object.values(this.state.delayedStopTypingTimers).forEach((t) => (t as Timer).abort()); Object.values(this.state.delayedStopTypingTimers).forEach((t) => t.abort());
} }
private static isVisible(state: IState): boolean { private static isVisible(state: IState): boolean {
@@ -133,7 +133,7 @@ export async function sendMessage(
const prom = doMaybeLocalRoomAction( const prom = doMaybeLocalRoomAction(
roomId, roomId,
(actualRoomId: string) => mxClient.sendMessage(actualRoomId, threadId, content!), (actualRoomId: string) => mxClient.sendMessage(actualRoomId, threadId, content),
mxClient, mxClient,
); );
@@ -127,7 +127,7 @@ const ExistingThreepid: React.FC<ExistingThreepidProps> = ({ mode, threepid, onC
threepid.medium === "email" threepid.medium === "email"
? _t("settings|general|error_revoke_email_discovery") ? _t("settings|general|error_revoke_email_discovery")
: _t("settings|general|error_revoke_msisdn_discovery"), : _t("settings|general|error_revoke_msisdn_discovery"),
}).then(); });
}, },
[changeBinding, threepid.medium], [changeBinding, threepid.medium],
); );
@@ -143,7 +143,7 @@ const ExistingThreepid: React.FC<ExistingThreepidProps> = ({ mode, threepid, onC
threepid.medium === "email" threepid.medium === "email"
? _t("settings|general|error_share_email_discovery") ? _t("settings|general|error_share_email_discovery")
: _t("settings|general|error_share_msisdn_discovery"), : _t("settings|general|error_share_msisdn_discovery"),
}).then(); });
}, },
[changeBinding, threepid.medium], [changeBinding, threepid.medium],
); );
@@ -96,6 +96,7 @@ export default class IntegrationManager extends React.Component<IProps, IState>
); );
} }
// oxlint-disable-next-line react/iframe-missing-sandbox
return <iframe title={_t("common|integration_manager")} src={this.props.url} onError={this.onError} />; return <iframe title={_t("common|integration_manager")} src={this.props.url} onError={this.onError} />;
} }
} }
@@ -289,7 +289,7 @@ export default class Notifications extends React.PureComponent<EmptyObject, ISta
} }
private async refreshRules(): Promise<Partial<IState>> { private async refreshRules(): Promise<Partial<IState>> {
const ruleSets = await MatrixClientPeg.safeGet().getPushRules()!; const ruleSets = await MatrixClientPeg.safeGet().getPushRules();
const categories: Record<string, RuleClass> = { const categories: Record<string, RuleClass> = {
[RuleId.Master]: RuleClass.Master, [RuleId.Master]: RuleClass.Master,
@@ -353,7 +353,7 @@ export default class Notifications extends React.PureComponent<EmptyObject, ISta
for (const rule of defaultRules[category]) { for (const rule of defaultRules[category]) {
const definition: VectorPushRuleDefinition = VectorPushRulesDefinitions[rule.rule_id]; const definition: VectorPushRuleDefinition = VectorPushRulesDefinitions[rule.rule_id];
const vectorState = definition.ruleToVectorState(rule)!; const vectorState = definition.ruleToVectorState(rule)!;
preparedNewState.vectorPushRules[category]!.push({ preparedNewState.vectorPushRules[category].push({
ruleId: rule.rule_id, ruleId: rule.rule_id,
rule, rule,
vectorState, vectorState,
@@ -363,7 +363,7 @@ export default class Notifications extends React.PureComponent<EmptyObject, ISta
} }
// Quickly sort the rules for display purposes // Quickly sort the rules for display purposes
preparedNewState.vectorPushRules[category]!.sort((a, b) => { preparedNewState.vectorPushRules[category].sort((a, b) => {
let idxA = RULE_DISPLAY_ORDER.indexOf(a.ruleId); let idxA = RULE_DISPLAY_ORDER.indexOf(a.ruleId);
let idxB = RULE_DISPLAY_ORDER.indexOf(b.ruleId); let idxB = RULE_DISPLAY_ORDER.indexOf(b.ruleId);
@@ -375,7 +375,7 @@ export default class Notifications extends React.PureComponent<EmptyObject, ISta
}); });
if (category === KEYWORD_RULE_CATEGORY) { if (category === KEYWORD_RULE_CATEGORY) {
preparedNewState.vectorPushRules[category]!.push({ preparedNewState.vectorPushRules[category].push({
ruleId: KEYWORD_RULE_ID, ruleId: KEYWORD_RULE_ID,
description: _t("settings|notifications|messages_containing_keywords"), description: _t("settings|notifications|messages_containing_keywords"),
vectorState: preparedNewState.vectorKeywordRuleInfo.vectorState, vectorState: preparedNewState.vectorKeywordRuleInfo.vectorState,
@@ -68,15 +68,7 @@ export const UserPersonalInfoSettings: React.FC<UserPersonalInfoSettingsProps> =
}, [client]); }, [client]);
useEffect(() => { useEffect(() => {
updateThreepids().then(); updateThreepids();
}, [updateThreepids]);
const onEmailsChange = useCallback(() => {
updateThreepids().then();
}, [updateThreepids]);
const onMsisdnsChange = useCallback(() => {
updateThreepids().then();
}, [updateThreepids]); }, [updateThreepids]);
if (!SettingsStore.getValue(UIFeature.ThirdPartyID)) return null; if (!SettingsStore.getValue(UIFeature.ThirdPartyID)) return null;
@@ -96,7 +88,7 @@ export const UserPersonalInfoSettings: React.FC<UserPersonalInfoSettingsProps> =
mode="hs" mode="hs"
medium={ThreepidMedium.Email} medium={ThreepidMedium.Email}
threepids={emails!} threepids={emails!}
onChange={onEmailsChange} onChange={updateThreepids}
disabled={!canMake3pidChanges} disabled={!canMake3pidChanges}
isLoading={loadingState === "loading"} isLoading={loadingState === "loading"}
/> />
@@ -116,7 +108,7 @@ export const UserPersonalInfoSettings: React.FC<UserPersonalInfoSettingsProps> =
mode="hs" mode="hs"
medium={ThreepidMedium.Phone} medium={ThreepidMedium.Phone}
threepids={phoneNumbers!} threepids={phoneNumbers!}
onChange={onMsisdnsChange} onChange={updateThreepids}
disabled={!canMake3pidChanges} disabled={!canMake3pidChanges}
isLoading={loadingState === "loading"} isLoading={loadingState === "loading"}
/> />
@@ -299,7 +299,7 @@ export const FilteredDeviceList = ({
]; ];
const onFilterOptionChange = (filterId: DeviceFilterKey): void => { const onFilterOptionChange = (filterId: DeviceFilterKey): void => {
onFilterChange(filterId === ALL_FILTER_ID ? undefined : (filterId as FilterVariation)); onFilterChange(filterId === ALL_FILTER_ID ? undefined : filterId);
}; };
const isAllSelected = selectedDeviceIds.length >= sortedDevices.length; const isAllSelected = selectedDeviceIds.length >= sortedDevices.length;
@@ -73,7 +73,7 @@ export const DiscoverySettings: React.FC = () => {
if (payload.action === "id_server_changed") { if (payload.action === "id_server_changed") {
setIdServerName(abbreviateUrl(client.getIdentityServerUrl())); setIdServerName(abbreviateUrl(client.getIdentityServerUrl()));
getThreepidState().then(); getThreepidState();
} }
}, },
[client, getThreepidState], [client, getThreepidState],
@@ -113,9 +113,7 @@ export const DiscoverySettings: React.FC = () => {
// User accepted all terms // User accepted all terms
setMustAgreeToTerms(false); setMustAgreeToTerms(false);
} catch (e) { } catch (e) {
logger.warn( logger.warn(`Unable to reach identity server at ${idServerUrl} to check for terms in Settings`);
`Unable to reach identity server at ${idServerUrl} to check ` + `for terms in Settings`,
);
logger.warn(e); logger.warn(e);
} }
} catch {} } catch {}
@@ -99,7 +99,7 @@ export default function NotificationSettings2(): JSX.Element {
<SettingsBanner <SettingsBanner
icon={<img src={NewAndImprovedIcon} alt="" width={12} />} icon={<img src={NewAndImprovedIcon} alt="" width={12} />}
action={_t("action|proceed")} action={_t("action|proceed")}
onAction={() => reconcile(model!)} onAction={() => reconcile(model)}
> >
{_t( {_t(
"settings|notifications|labs_notice_prompt", "settings|notifications|labs_notice_prompt",
@@ -46,7 +46,7 @@ interface IProps {
pipMode?: boolean; pipMode?: boolean;
// Used for dragging the PiP LegacyCallView // Used for dragging the PiP LegacyCallView
onMouseDownOnHeader?: (event: React.MouseEvent<Element, MouseEvent>) => void; onMouseDownOnHeader?: (event: React.MouseEvent) => void;
showApps?: boolean; showApps?: boolean;
@@ -72,7 +72,7 @@ const SecondaryCallInfo: React.FC<ISecondaryCallInfoProps> = ({ callRoom }) => {
interface LegacyCallViewHeaderProps { interface LegacyCallViewHeaderProps {
pipMode?: boolean; pipMode?: boolean;
callRooms: [Room, Room | null]; callRooms: [Room, Room | null];
onPipMouseDown?: (event: React.MouseEvent<Element, MouseEvent>) => void; onPipMouseDown?: (event: React.MouseEvent) => void;
onExpand?: () => void; onExpand?: () => void;
onPin?: () => void; onPin?: () => void;
onMaximize?: () => void; onMaximize?: () => void;
@@ -25,4 +25,4 @@ function getDisplayUserIdentifier(
// customisation points that make up `IUserIdentifierCustomisations`. // customisation points that make up `IUserIdentifierCustomisations`.
export default { export default {
getDisplayUserIdentifier, getDisplayUserIdentifier,
} as UserIdentifierCustomisations; } satisfies UserIdentifierCustomisations;
+1 -1
View File
@@ -220,7 +220,7 @@ export function formatRangeAsLink(range: Range, text?: string): void {
replaceRangeAndMoveCaret(range, newParts, 0); replaceRangeAndMoveCaret(range, newParts, 0);
} else { } else {
// We set offset to -1 here so that the caret lands between the brackets // We set offset to -1 here so that the caret lands between the brackets
replaceRangeAndMoveCaret(range, [partCreator.plain("[" + range.text + "]" + "(" + (text ?? "") + ")")], -1); replaceRangeAndMoveCaret(range, [partCreator.plain(`[${range.text}](${text ?? ""})`)], -1);
} }
} }
+6 -6
View File
@@ -83,9 +83,9 @@ function reconcileLine(lineContainer: ChildNode, parts: Part[]): void {
currentNode = isFirst ? lineContainer.firstChild : currentNode!.nextSibling; currentNode = isFirst ? lineContainer.firstChild : currentNode!.nextSibling;
if (needsCaretNodeBefore(part, prevPart)) { if (needsCaretNodeBefore(part, prevPart)) {
if (isCaretNode(currentNode as Element)) { if (isCaretNode(currentNode)) {
updateCaretNode(currentNode!); updateCaretNode(currentNode);
currentNode = currentNode!.nextSibling; currentNode = currentNode.nextSibling;
} else { } else {
lineContainer.insertBefore(createCaretNode(), currentNode); lineContainer.insertBefore(createCaretNode(), currentNode);
} }
@@ -106,9 +106,9 @@ function reconcileLine(lineContainer: ChildNode, parts: Part[]): void {
} }
if (needsCaretNodeAfter(part, part === lastPart)) { if (needsCaretNodeAfter(part, part === lastPart)) {
if (isCaretNode(currentNode?.nextSibling as Element)) { if (isCaretNode(currentNode?.nextSibling)) {
currentNode = currentNode!.nextSibling; currentNode = currentNode.nextSibling;
updateCaretNode(currentNode as HTMLElement); updateCaretNode(currentNode);
} else { } else {
const caretNode = createCaretNode(); const caretNode = createCaretNode();
insertAfter(currentNode as HTMLElement, caretNode); insertAfter(currentNode as HTMLElement, caretNode);
+2 -2
View File
@@ -177,9 +177,9 @@ export function textSerialize(model: EditorModel): string {
case Type.RoomPill: case Type.RoomPill:
// Here we use the resourceId for compatibility with non-rich text clients // Here we use the resourceId for compatibility with non-rich text clients
// See https://github.com/vector-im/element-web/issues/16660 // See https://github.com/vector-im/element-web/issues/16660
return text + `${part.resourceId}`; return text + part.resourceId;
case Type.UserPill: case Type.UserPill:
return text + `${part.text}`; return text + part.text;
} }
}, ""); }, "");
} }
+1
View File
@@ -182,6 +182,7 @@ export class BadgeOverlayRenderer extends IconRenderer {
(blob) => { (blob) => {
if (blob) { if (blob) {
resolve(blob.arrayBuffer()); resolve(blob.arrayBuffer());
return;
} }
reject(new Error("Could not render badge overlay as blob")); reject(new Error("Could not render badge overlay as blob"));
}, },
+1
View File
@@ -189,4 +189,5 @@ export function useEventEmitterAsyncState<T, Events extends string, Arguments ex
/** /**
* Indicates that the callback for `useEventEmitterAsyncState` is not changing the value of the state. * Indicates that the callback for `useEventEmitterAsyncState` is not changing the value of the state.
*/ */
// oxlint-disable-next-line typescript/no-extraneous-class
export class NoChange {} export class NoChange {}
+1 -1
View File
@@ -25,7 +25,7 @@ export async function isRoomEncrypted(room: Room, cryptoApi: CryptoApi): Promise
if (room instanceof LocalRoom) { if (room instanceof LocalRoom) {
// For local room check the state. // For local room check the state.
// The crypto check fails because the room ID is not valid (it is a local id) // The crypto check fails because the room ID is not valid (it is a local id)
return (room as LocalRoom).isEncryptionEnabled(); return room.isEncryptionEnabled();
} }
return await cryptoApi.isEncryptionEnabledInRoom(room.roomId); return await cryptoApi.isEncryptionEnabledInRoom(room.roomId);
+1
View File
@@ -29,6 +29,7 @@ export const useSpaceResults = (space: Room | undefined, query: string): [Hierar
let unmounted = false; let unmounted = false;
(async (): Promise<void> => { (async (): Promise<void> => {
// oxlint-disable-next-line no-unmodified-loop-condition
while (hierarchy?.canLoadMore && !unmounted && space === hierarchy.root) { while (hierarchy?.canLoadMore && !unmounted && space === hierarchy.root) {
await hierarchy.load(); await hierarchy.load();
if (hierarchy.canLoadMore) hierarchy.load(); // start next load so that the loading attribute is right if (hierarchy.canLoadMore) hierarchy.load(); // start next load so that the loading attribute is right
+1
View File
@@ -458,6 +458,7 @@ export default class EventIndex extends EventEmitter {
let idle = false; let idle = false;
// oxlint-disable-next-line no-unmodified-loop-condition
while (!cancelled) { while (!cancelled) {
let sleepTime = SettingsStore.getValueAt(SettingLevel.DEVICE, "crawlerSleepTime"); let sleepTime = SettingsStore.getValueAt(SettingLevel.DEVICE, "crawlerSleepTime");
+2 -1
View File
@@ -226,6 +226,7 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
// The widget might still be initializing, so wait for it in an async // The widget might still be initializing, so wait for it in an async
// event loop. We need the messaging to be both present and started // event loop. We need the messaging to be both present and started
// (have a connected widget API), so register listeners for both cases. // (have a connected widget API), so register listeners for both cases.
// oxlint-disable-next-line no-unmodified-loop-condition
while (!messaging?.widgetApi) { while (!messaging?.widgetApi) {
if (messaging) logger.debug(`Messaging present but not yet started for ${this.widgetUid}`); if (messaging) logger.debug(`Messaging present but not yet started for ${this.widgetUid}`);
else logger.debug(`No messaging yet for ${this.widgetUid}`); else logger.debug(`No messaging yet for ${this.widgetUid}`);
@@ -430,7 +431,7 @@ export class JitsiCall extends Call {
const event = this.room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, this.client.getUserId()!); const event = this.room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, this.client.getUserId()!);
const content = event?.getContent<JitsiCallMemberContent>(); const content = event?.getContent<JitsiCallMemberContent>();
const expiresAt = typeof content?.expires_ts === "number" ? content.expires_ts : -Infinity; const expiresAt = typeof content?.expires_ts === "number" ? content.expires_ts : -Infinity;
const devices = expiresAt > Date.now() && Array.isArray(content?.devices) ? content!.devices : []; const devices = expiresAt > Date.now() && Array.isArray(content?.devices) ? content.devices : [];
const newDevices = fn(devices); const newDevices = fn(devices);
if (newDevices !== null) { if (newDevices !== null) {
+1 -1
View File
@@ -13,7 +13,7 @@ export class ConfigApi implements IConfigApi {
public get<K extends keyof Config>(key: K): Config[K]; public get<K extends keyof Config>(key: K): Config[K];
public get<K extends keyof Config = never>(key?: K): Config | Config[K] { public get<K extends keyof Config = never>(key?: K): Config | Config[K] {
if (key === undefined) { if (key === undefined) {
return SdkConfig.get() as Config; return SdkConfig.get();
} }
return SdkConfig.get(key); return SdkConfig.get(key);
} }
+1 -1
View File
@@ -143,7 +143,7 @@ export class ProxiedModuleApi implements ModuleApi {
return { return {
homeserverUrl: hsUrl, homeserverUrl: hsUrl,
userId: creds.user_id!, userId: creds.user_id,
deviceId: creds.device_id!, deviceId: creds.device_id!,
accessToken: creds.access_token!, accessToken: creds.access_token!,
}; };
@@ -16,6 +16,7 @@ export interface IContentRules {
externalRules: IAnnotatedPushRule[]; externalRules: IAnnotatedPushRule[];
} }
// oxlint-disable-next-line typescript/no-extraneous-class
export class ContentRules { export class ContentRules {
/** /**
* Extract the keyword rules from a list of rules, and parse them * Extract the keyword rules from a list of rules, and parse them
@@ -19,6 +19,7 @@ export interface PushRuleActions {
highlight?: boolean; highlight?: boolean;
} }
// oxlint-disable-next-line typescript/no-extraneous-class
export class NotificationUtils { export class NotificationUtils {
// Encodes a dictionary of { // Encodes a dictionary of {
// "notify": true/false, // "notify": true/false,
@@ -21,6 +21,7 @@ export enum VectorState {
Loud = "loud", Loud = "loud",
} }
// oxlint-disable-next-line typescript/no-extraneous-class
export class PushRuleVectorState { export class PushRuleVectorState {
// Backwards compatibility (things should probably be using the enum above instead) // Backwards compatibility (things should probably be using the enum above instead)
public static OFF = VectorState.Off; public static OFF = VectorState.Off;
@@ -13,6 +13,7 @@ import { NotificationUtils } from "./NotificationUtils";
const encodeActions = NotificationUtils.encodeActions; const encodeActions = NotificationUtils.encodeActions;
// oxlint-disable-next-line typescript/no-extraneous-class
export class StandardActions { export class StandardActions {
public static ACTION_NOTIFY = encodeActions({ notify: true }); public static ACTION_NOTIFY = encodeActions({ notify: true });
public static ACTION_NOTIFY_DEFAULT_SOUND = encodeActions({ notify: true, sound: "default" }); public static ACTION_NOTIFY_DEFAULT_SOUND = encodeActions({ notify: true, sound: "default" });
+1 -1
View File
@@ -421,7 +421,7 @@ export class IndexedDBLogStore {
resolve(); resolve();
}; };
txn.onerror = () => { txn.onerror = () => {
reject(new Error("Failed to delete logs for " + `'${id}' : ${query.error?.message}`)); reject(new Error(`Failed to delete logs for '${id}' : ${query.error?.message}`));
}; };
// delete last modified entries // delete last modified entries
const lastModStore = txn.objectStore("logslastmod"); const lastModStore = txn.objectStore("logslastmod");
+1 -1
View File
@@ -22,7 +22,7 @@ export const ambiguousLinkTooltipRenderer: RendererMap = {
const href = anchor.attribs["href"]; const href = anchor.attribs["href"];
if (href && href !== getSingleTextContentNode(anchor)) { if (href && href !== getSingleTextContentNode(anchor)) {
let tooltip = href as string; let tooltip = href;
try { try {
tooltip = new URL(href, window.location.href).toString(); tooltip = new URL(href, window.location.href).toString();
} catch { } catch {
+5 -5
View File
@@ -125,6 +125,7 @@ type HandlerMap = Partial<{
* feature may be reported as disabled even though a user has specifically requested it * feature may be reported as disabled even though a user has specifically requested it
* be enabled). * be enabled).
*/ */
// oxlint-disable-next-line typescript/no-extraneous-class
export default class SettingsStore { export default class SettingsStore {
// We support watching settings for changes, and do this by tracking which callbacks have // We support watching settings for changes, and do this by tracking which callbacks have
// been given to us. We end up returning the callbackRef to the caller so they can unsubscribe // been given to us. We end up returning the callbackRef to the caller so they can unsubscribe
@@ -263,7 +264,7 @@ export default class SettingsStore {
if (roomId === null) { if (roomId === null) {
// Unregister all existing watchers and register the new one // Unregister all existing watchers and register the new one
rooms.forEach((roomId) => { rooms.forEach((roomId) => {
SettingsStore.unwatchSetting(this.monitors.get(settingName)!.get(roomId)!); SettingsStore.unwatchSetting(this.monitors.get(settingName)!.get(roomId));
}); });
this.monitors.get(settingName)!.clear(); this.monitors.get(settingName)!.clear();
registerWatcher(); registerWatcher();
@@ -334,10 +335,10 @@ export default class SettingsStore {
SettingsStore.isFeature(settingName) && SettingsStore.isFeature(settingName) &&
SettingsStore.getValueAt(SettingLevel.CONFIG, settingName, null, true, true) !== false SettingsStore.getValueAt(SettingLevel.CONFIG, settingName, null, true, true) !== false
) { ) {
const betaInfo = SETTINGS[settingName]!.betaInfo; const betaInfo = SETTINGS[settingName].betaInfo;
if (betaInfo) { if (betaInfo) {
betaInfo.requiresRefresh = betaInfo.requiresRefresh =
betaInfo.requiresRefresh ?? SETTINGS[settingName]!.controller instanceof ReloadOnChangeController; betaInfo.requiresRefresh ?? SETTINGS[settingName].controller instanceof ReloadOnChangeController;
} }
return betaInfo; return betaInfo;
} }
@@ -868,8 +869,7 @@ export default class SettingsStore {
private static getHandler(settingName: SettingKey, level: SettingLevel): SettingsHandler | null { private static getHandler(settingName: SettingKey, level: SettingLevel): SettingsHandler | null {
const handlers = SettingsStore.getHandlers(settingName); const handlers = SettingsStore.getHandlers(settingName);
if (!handlers[level]) return null; return handlers[level] ?? null;
return handlers[level]!;
} }
private static getHandlers(settingName: SettingKey): HandlerMap { private static getHandlers(settingName: SettingKey): HandlerMap {
@@ -56,7 +56,7 @@ export default class MediaPreviewConfigController extends MatrixClientBackedCont
// Save an account data fetch if we have all the values. // Save an account data fetch if we have all the values.
if (calculatedConfig.invite_avatars && calculatedConfig.media_previews) { if (calculatedConfig.invite_avatars && calculatedConfig.media_previews) {
return calculatedConfig as MediaPreviewConfig; return calculatedConfig;
} }
// We're missing some keys. // We're missing some keys.
+1 -1
View File
@@ -123,4 +123,4 @@ let singletonLifecycleStore: LifecycleStore | null = null;
if (!singletonLifecycleStore) { if (!singletonLifecycleStore) {
singletonLifecycleStore = new LifecycleStore(); singletonLifecycleStore = new LifecycleStore();
} }
export default singletonLifecycleStore!; export default singletonLifecycleStore;
+1 -1
View File
@@ -145,7 +145,7 @@ export class MemberListStore {
return true; return true;
} }
const enablePresenceByHsUrl = SdkConfig.get("enable_presence_by_hs_url"); const enablePresenceByHsUrl = SdkConfig.get("enable_presence_by_hs_url");
return enablePresenceByHsUrl?.[this.stores.client!.baseUrl] ?? true; return enablePresenceByHsUrl?.[this.stores.client.baseUrl] ?? true;
} }
/** /**
+1 -1
View File
@@ -66,7 +66,7 @@ export class ModalWidgetStore extends AsyncStoreWithClient<IState> {
/* priority = */ false, /* priority = */ false,
/* static = */ true, /* static = */ true,
); );
this.modalInstance!.finished.then(([success, data]) => { this.modalInstance.finished.then(([success, data]) => {
this.closeModalWidget(sourceWidget, widgetRoomId, success && data ? data : { "m.exited": true }); this.closeModalWidget(sourceWidget, widgetRoomId, success && data ? data : { "m.exited": true });
this.openSourceWidgetId = null; this.openSourceWidgetId = null;
+1
View File
@@ -512,6 +512,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
this.stopPollingLocation(); this.stopPollingLocation();
// kill live beacons when location permissions are revoked // kill live beacons when location permissions are revoked
// oxlint-disable-next-line promise/no-promise-in-callback
await Promise.all(this.liveBeaconIds.map(this.stopBeacon)); await Promise.all(this.liveBeaconIds.map(this.stopBeacon));
}; };
@@ -45,4 +45,5 @@ export class RoomScrollStateStore {
if (window.mxRoomScrollStateStore === undefined) { if (window.mxRoomScrollStateStore === undefined) {
window.mxRoomScrollStateStore = new RoomScrollStateStore(); window.mxRoomScrollStateStore = new RoomScrollStateStore();
} }
// oxlint-disable-next-line typescript/no-unnecessary-type-assertion
export default window.mxRoomScrollStateStore!; export default window.mxRoomScrollStateStore!;
+1 -1
View File
@@ -63,7 +63,7 @@ export default class ThreepidInviteStore extends EventEmitter {
} }
public storeInvite(roomId: string, wireInvite: IThreepidInviteWireFormat): IThreepidInvite { public storeInvite(roomId: string, wireInvite: IThreepidInviteWireFormat): IThreepidInvite {
const invite = <IPersistedThreepidInvite>{ roomId, ...wireInvite }; const invite: IPersistedThreepidInvite = { roomId, ...wireInvite };
const id = this.generateIdOf(invite); const id = this.generateIdOf(invite);
localStorage.setItem(`${STORAGE_PREFIX}${id}`, JSON.stringify(invite)); localStorage.setItem(`${STORAGE_PREFIX}${id}`, JSON.stringify(invite));
return this.translateInvite(invite); return this.translateInvite(invite);
+1
View File
@@ -107,4 +107,5 @@ let singletonWidgetEchoStore: WidgetEchoStore | null = null;
if (!singletonWidgetEchoStore) { if (!singletonWidgetEchoStore) {
singletonWidgetEchoStore = new WidgetEchoStore(); singletonWidgetEchoStore = new WidgetEchoStore();
} }
// oxlint-disable-next-line typescript/no-unnecessary-type-assertion
export default singletonWidgetEchoStore!; export default singletonWidgetEchoStore!;

Some files were not shown because too many files have changed in this diff Show More