Switch from eslint to oxlint (#34166)

* 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

* Make sonar happy

* Fix new lints
This commit is contained in:
Michael Telatynski
2026-07-10 09:47:41 +00:00
committed by GitHub
parent 549dd1b7a8
commit 15c85ec50f
176 changed files with 1294 additions and 2876 deletions
-97
View File
@@ -1,97 +0,0 @@
module.exports = {
plugins: ["matrix-org", "n"],
extends: ["plugin:matrix-org/javascript"],
parserOptions: {
ecmaVersion: 2021,
project: ["tsconfig.json"],
},
env: {
es6: true,
node: true,
// we also have some browser code (ie. the preload script)
browser: true,
},
// NOTE: These rules are frozen and new rules should not be added here.
// New changes belong in https://github.com/matrix-org/eslint-plugin-matrix-org/
rules: {
"quotes": "off",
"indent": "off",
"prefer-promise-reject-errors": "off",
"no-async-promise-executor": "off",
"n/file-extension-in-import": ["error", "always"],
"unicorn/prefer-node-protocol": ["error"],
},
overrides: [
{
files: ["src/**/*.ts"],
extends: ["plugin:matrix-org/typescript"],
rules: {
// Things we do that break the ideal style
"prefer-promise-reject-errors": "off",
"quotes": "off",
"@typescript-eslint/no-explicit-any": "off",
// We're okay with assertion errors when we ask for them
"@typescript-eslint/no-non-null-assertion": "off",
},
},
{
files: ["hak/**/*.ts"],
extends: ["plugin:matrix-org/typescript"],
parserOptions: {
project: ["hak/tsconfig.json"],
},
rules: {
// Things we do that break the ideal style
"prefer-promise-reject-errors": "off",
"quotes": "off",
"n/file-extension-in-import": "off",
"@typescript-eslint/no-explicit-any": "off",
// We're okay with assertion errors when we ask for them
"@typescript-eslint/no-non-null-assertion": "off",
},
},
{
files: ["scripts/**/*.ts"],
extends: ["plugin:matrix-org/typescript"],
parserOptions: {
project: ["scripts/tsconfig.json"],
},
rules: {
// Things we do that break the ideal style
"prefer-promise-reject-errors": "off",
"quotes": "off",
"n/file-extension-in-import": "off",
"@typescript-eslint/no-explicit-any": "off",
// We're okay with assertion errors when we ask for them
"@typescript-eslint/no-non-null-assertion": "off",
},
},
{
files: ["playwright/**/*.ts"],
extends: ["plugin:matrix-org/typescript"],
parserOptions: {
project: ["playwright/tsconfig.json"],
},
rules: {
// Things we do that break the ideal style
"prefer-promise-reject-errors": "off",
"quotes": "off",
"@typescript-eslint/no-explicit-any": "off",
// We're okay with assertion errors when we ask for them
"@typescript-eslint/no-non-null-assertion": "off",
},
},
{
files: ["src/**/*.test.ts", "electron-builder.ts", "vitest.config.ts"],
extends: ["plugin:matrix-org/typescript"],
parserOptions: {
project: ["tsconfig.node.json"],
},
},
],
};
-4
View File
@@ -1,4 +0,0 @@
{
"*": "oxfmt --no-error-on-unmatched-pattern",
"*.(ts|tsx)": ["eslint --fix"]
}
+7
View File
@@ -1,3 +1,10 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"],
};
+7
View File
@@ -1,3 +1,10 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import * as os from "node:os";
import * as fs from "node:fs";
import * as path from "node:path";
+2 -1
View File
@@ -9,7 +9,8 @@
"lib": ["es2022"],
"types": ["node"],
"allowImportingTsExtensions": true,
"skipLibCheck": true
"skipLibCheck": true,
"noEmit": true
},
"include": ["../scripts/@types/*.d.ts", "./**/*.ts"]
}
+2 -14
View File
@@ -29,9 +29,7 @@
"fetch": "pnpm run mkdirs && node scripts/fetch-package.ts",
"asar-webapp": "asar p webapp webapp.asar",
"start": "nx start",
"lint": "pnpm lint:types && pnpm lint:js",
"lint:js": "eslint --max-warnings 0 src hak playwright scripts",
"lint:js-fix": "eslint --fix --max-warnings 0 src hak playwright scripts",
"lint": "pnpm lint:types",
"lint:types": "pnpm lint:types:src && pnpm lint:types:node && pnpm lint:types:test && pnpm lint:types:scripts && pnpm lint:types:hak",
"lint:types:src": "tsc --noEmit",
"lint:types:node": "tsc --noEmit -p tsconfig.node.json",
@@ -75,14 +73,11 @@
"@electron/asar": "4.2.0",
"@electron/fuses": "^2.1.1",
"@playwright/test": "catalog:",
"@stylistic/eslint-plugin": "^5.0.0",
"@types/auto-launch": "^5.0.1",
"@types/counterpart": "^0.18.1",
"@types/minimist": "^1.2.1",
"@types/node": "catalog:",
"@types/pacote": "^11.1.1",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"app-builder-lib": "26.15.3",
"chokidar": "^5.0.0",
"detect-libc": "^2.0.0",
@@ -90,12 +85,6 @@
"electron-builder": "26.15.3",
"electron-builder-squirrel-windows": "26.15.3",
"electron-devtools-installer": "^4.0.0",
"eslint": "^8.26.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-matrix-org": "^3.0.0",
"eslint-plugin-n": "^17.12.0",
"eslint-plugin-unicorn": "^56.0.0",
"glob": "^13.0.0",
"matrix-web-i18n": "catalog:",
"memfs": "^4.57.2",
@@ -112,8 +101,7 @@
},
"nx": {
"includedScripts": [
"lint:types",
"lint:js"
"lint:types"
]
}
}
+7
View File
@@ -1,5 +1,12 @@
#!/usr/bin/env node
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
// copies resources into the lib directory.
import parseArgs from "minimist";
+6
View File
@@ -1,4 +1,10 @@
#!/usr/bin/env node
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import * as path from "node:path";
import { createWriteStream, promises as fs } from "node:fs";
@@ -1,4 +1,10 @@
#!/usr/bin/env node
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
/**
* Script to generate incremental Nightly build versions, based on the latest Nightly build version of that kind.
+6
View File
@@ -1,4 +1,10 @@
#!/usr/bin/env node
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
/*
* Checks for the presence of a webapp, inspects its version and prints it
+6
View File
@@ -1,4 +1,10 @@
#!/usr/bin/env node
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
/*
* Checks for the presence of a webapp, inspects its version and sets the
+2 -1
View File
@@ -10,7 +10,8 @@
"strict": true,
"lib": ["es2022"],
"types": ["node"],
"allowImportingTsExtensions": true
"allowImportingTsExtensions": true,
"noEmit": true
},
"include": ["../src/@types", "./**/*.ts"]
}
+2
View File
@@ -385,11 +385,13 @@ app.on("ready", async () => {
desktopCapturer
.getSources({ types: ["screen", "window"] })
.then((sources) => {
// oxlint-disable-next-line promise/no-callback-in-promise
callback({ video: sources[0] });
})
.catch((err) => {
// If the user cancels the dialog an error occurs "Failed to get sources"
console.error("Wayland: failed to get user-selected source:", err);
// oxlint-disable-next-line promise/no-callback-in-promise
callback({ video: { id: "", name: "" } }); // The promise does not return if no dummy is passed here as source
});
} else {
+1 -1
View File
@@ -144,7 +144,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
}));
break;
case "callDisplayMediaCallback":
await getDisplayMediaCallback()?.({ video: args[0] });
getDisplayMediaCallback()?.({ video: args[0] });
setDisplayMediaCallback(null);
ret = null;
break;
+2 -2
View File
@@ -113,7 +113,7 @@ export class AppLocalization {
locales = [locales];
}
const loadedLocales = locales.filter((locale) => {
const chosenLocale = locales.find((locale) => {
const translations = this.fetchTranslationJson(locale);
if (translations !== null) {
counterpart.registerTranslations(locale, translations);
@@ -121,7 +121,7 @@ export class AppLocalization {
return !!translations;
});
counterpart.setLocale(loadedLocales[0]);
counterpart.setLocale(chosenLocale!);
this.store.set(AppLocalization.STORE_KEY, locales);
this.resetLocalizedUI();
+1 -1
View File
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
// This file is compiled to CommonJS rather than ESM otherwise the browser chokes on the import statement.
import { ipcRenderer, contextBridge, IpcRendererEvent } from "electron";
import { ipcRenderer, contextBridge, type IpcRendererEvent } from "electron";
import type { ConfigOptions } from "./config.js" with { "resolution-mode": "import" };
// Expose only expected IPC wrapper APIs to the renderer process to avoid
-18
View File
@@ -1,18 +0,0 @@
src/vector/modernizr.cjs
test/end-to-end-tests/node_modules/
test/end-to-end-tests/element/
test/end-to-end-tests/synapse/
test/end-to-end-tests/lib/
# Legacy skinning file that some people might still have
src/component-index.js
# Auto-generated file
src/modules.ts
src/modules.js
# Test result files
/playwright/test-results/
/playwright/html-report/
# Shared components generated files
/packages/shared-components/dist/
/packages/shared-components/src/i18n/i18nKeys.d.ts
/packages/shared-components/typedoc/
-349
View File
@@ -1,349 +0,0 @@
/*
Copyright 2025 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
module.exports = {
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"],
parserOptions: {
project: ["./tsconfig.json"],
tsconfigRootDir: __dirname,
},
env: {
browser: true,
node: true,
},
globals: {
LANGUAGES_FILE: "readonly",
},
rules: {
// Things we do that break the ideal style
"no-constant-condition": "off",
"prefer-promise-reject-errors": "off",
"no-async-promise-executor": "off",
"no-extra-boolean-cast": "off",
// Bind or arrow functions in props causes performance issues (but we
// currently use them in some places).
// It's disabled here, but we should using it sparingly.
"react/jsx-no-bind": "off",
"react/jsx-key": ["error"],
"no-restricted-properties": [
"error",
...buildRestrictedPropertiesOptions(
["window.innerHeight", "window.innerWidth", "window.visualViewport"],
"Use UIStore to access window dimensions instead.",
),
...buildRestrictedPropertiesOptions(
["React.forwardRef", "*.forwardRef", "forwardRef"],
"Use ref props instead.",
),
...buildRestrictedPropertiesOptions(
["*.mxcUrlToHttp", "*.getHttpUriForMxc"],
"Use Media helper instead to centralise access for customisation.",
),
...buildRestrictedPropertiesOptions(["window.setImmediate"], "Use setTimeout instead."),
],
"no-restricted-globals": [
"error",
{
name: "setImmediate",
message: "Use setTimeout instead.",
},
{
name: "Buffer",
message: "Buffer is not available in the web.",
},
],
"import/no-duplicates": ["error"],
// Ban matrix-js-sdk/src imports in favour of matrix-js-sdk/src/matrix imports to prevent unleashing hell.
// Ban compound-design-tokens raw svg imports in favour of their React component counterparts
"no-restricted-imports": [
"error",
{
paths: [
{
name: "react",
importNames: ["forwardRef"],
message: "Use ref props instead.",
},
{
name: "@testing-library/react",
message: "Please use jest-matrix-react instead",
},
{
name: "matrix-js-sdk",
message: "Please use matrix-js-sdk/src/matrix instead",
},
{
name: "matrix-js-sdk/",
message: "Please use matrix-js-sdk/src/matrix instead",
},
{
name: "matrix-js-sdk/src",
message: "Please use matrix-js-sdk/src/matrix instead",
},
{
name: "matrix-js-sdk/src/",
message: "Please use matrix-js-sdk/src/matrix instead",
},
{
name: "matrix-js-sdk/src/index",
message: "Please use matrix-js-sdk/src/matrix instead",
},
{
name: "emojibase-regex",
message:
"This regex doesn't actually test for emoji. See the docs at https://emojibase.dev/docs/regex/ and prefer our own EMOJI_REGEX from HtmlUtils.",
},
],
patterns: [
{
group: [
"matrix-js-sdk/src/**",
"!matrix-js-sdk/src/matrix",
"!matrix-js-sdk/src/crypto-api",
"!matrix-js-sdk/src/types",
"!matrix-js-sdk/src/testing",
"!matrix-js-sdk/src/utils/**",
"matrix-js-sdk/src/utils/internal/**",
"matrix-js-sdk/lib",
"matrix-js-sdk/lib/",
"matrix-js-sdk/lib/**",
// XXX: Temporarily allow these as they are not available via the main export
"!matrix-js-sdk/src/logger",
"!matrix-js-sdk/src/errors",
"!matrix-js-sdk/src/utils",
"!matrix-js-sdk/src/version-support",
"!matrix-js-sdk/src/randomstring",
"!matrix-js-sdk/src/sliding-sync",
"!matrix-js-sdk/src/browser-index",
"!matrix-js-sdk/src/feature",
"!matrix-js-sdk/src/NamespacedValue",
"!matrix-js-sdk/src/ReEmitter",
"!matrix-js-sdk/src/event-mapper",
"!matrix-js-sdk/src/interactive-auth",
"!matrix-js-sdk/src/secret-storage",
"!matrix-js-sdk/src/room-hierarchy",
"!matrix-js-sdk/src/rendezvous",
"!matrix-js-sdk/src/indexeddb-worker",
"!matrix-js-sdk/src/pushprocessor",
"!matrix-js-sdk/src/extensible_events_v1",
"!matrix-js-sdk/src/extensible_events_v1/PollStartEvent",
"!matrix-js-sdk/src/extensible_events_v1/PollResponseEvent",
"!matrix-js-sdk/src/extensible_events_v1/PollEndEvent",
"!matrix-js-sdk/src/extensible_events_v1/InvalidEventError",
"!matrix-js-sdk/src/webrtc",
"!matrix-js-sdk/src/webrtc/call",
"!matrix-js-sdk/src/webrtc/callFeed",
"!matrix-js-sdk/src/webrtc/mediaHandler",
"!matrix-js-sdk/src/webrtc/callEventTypes",
"!matrix-js-sdk/src/webrtc/callEventHandler",
"!matrix-js-sdk/src/webrtc/groupCallEventHandler",
"!matrix-js-sdk/src/models",
"!matrix-js-sdk/src/models/read-receipt",
"!matrix-js-sdk/src/models/relations-container",
"!matrix-js-sdk/src/models/related-relations",
"!matrix-js-sdk/src/matrixrtc",
],
message: "Please use matrix-js-sdk/src/matrix instead",
},
{
group: ["emojibase-regex/emoji*"],
message:
"This regex doesn't actually test for emoji. See the docs at https://emojibase.dev/docs/regex/ and prefer our own EMOJI_REGEX from HtmlUtils.",
},
{
group: ["@vector-im/compound-design-tokens/icons/*"],
message: "Please use @vector-im/compound-design-tokens/assets/web/icons/* instead",
},
{
group: ["**/packages/shared-components/**", "../packages/shared-components/**"],
message: "Please use @element-hq/web-shared-components",
},
],
},
],
// There are too many a11y violations to fix at once
// Turn violated rules off until they are fixed
"jsx-a11y/aria-activedescendant-has-tabindex": "off",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/interactive-supports-focus": "off",
"jsx-a11y/media-has-caption": "off",
"jsx-a11y/mouse-events-have-key-events": "off",
"jsx-a11y/no-autofocus": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"jsx-a11y/no-noninteractive-element-to-interactive-role": "off",
"jsx-a11y/no-noninteractive-tabindex": "off",
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/role-supports-aria-props": "off",
"matrix-org/require-copyright-header": "error",
"react-compiler/react-compiler": "error",
},
overrides: [
{
files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}", "playwright/**/*.ts", "*.ts"],
extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"],
rules: {
"@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }],
"@typescript-eslint/explicit-function-return-type": [
"error",
{
allowExpressions: true,
},
],
// Things we do that break the ideal style
"prefer-promise-reject-errors": "off",
"no-extra-boolean-cast": "off",
// Remove Babel things manually due to override limitations
"@babel/no-invalid-this": ["off"],
// We're okay being explicit at the moment
"@typescript-eslint/no-empty-interface": "off",
// We disable this while we're transitioning
"@typescript-eslint/no-explicit-any": "off",
// We'd rather not do this but we do
"@typescript-eslint/ban-ts-comment": "off",
// We're okay with assertion errors when we ask for them
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-empty-object-type": [
"error",
{
// We do this sometimes to brand interfaces
allowInterfaces: "with-single-extends",
},
],
},
},
{
files: [
"src/**/*.test.{ts,tsx}",
"src/test/**/*.{ts,tsx}",
"src/**/__mocks__/*.{ts,tsx}",
"test/**/*.{ts,tsx}",
"playwright/**/*.ts",
],
extends: [],
rules: {
// We don't need super strict typing in test utilities
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/no-empty-object-type": "off",
"@typescript-eslint/unbound-method": "off",
// These are fine in tests
"no-restricted-globals": "off",
"react-compiler/react-compiler": "off",
},
},
{
files: ["test/**/*.{ts,tsx}"],
extends: ["plugin:matrix-org/jest"],
rules: {
// Disabled tests are a reality for now but as soon as all of the xits are
// eliminated, we should enforce this.
"jest/no-disabled-tests": "off",
// Also treat "oldBackendOnly" as a test function.
// Used in some crypto tests.
"jest/no-standalone-expect": [
"error",
{
additionalTestBlockFunctions: ["beforeAll", "beforeEach", "oldBackendOnly"],
},
],
},
},
{
files: ["playwright/**/*.ts"],
parserOptions: {
project: ["./playwright/tsconfig.json"],
},
rules: {
"react-hooks/rules-of-hooks": ["off"],
"@typescript-eslint/no-floating-promises": ["error"],
},
},
{
files: ["module_system/**/*.{ts,tsx}"],
parserOptions: {
project: ["./tsconfig.module_system.json"],
},
extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"],
// NOTE: These rules are frozen and new rules should not be added here.
// New changes belong in https://github.com/matrix-org/eslint-plugin-matrix-org/
rules: {
// Things we do that break the ideal style
"prefer-promise-reject-errors": "off",
"quotes": "off",
// We disable this while we're transitioning
"@typescript-eslint/no-explicit-any": "off",
// We're okay with assertion errors when we ask for them
"@typescript-eslint/no-non-null-assertion": "off",
// Ban matrix-js-sdk/src imports in favour of matrix-js-sdk/src/matrix imports to prevent unleashing hell.
"no-restricted-imports": [
"error",
{
paths: [
{
name: "matrix-js-sdk",
message: "Please use matrix-js-sdk/src/matrix instead",
},
{
name: "matrix-js-sdk/",
message: "Please use matrix-js-sdk/src/matrix instead",
},
{
name: "matrix-js-sdk/src",
message: "Please use matrix-js-sdk/src/matrix instead",
},
{
name: "matrix-js-sdk/src/",
message: "Please use matrix-js-sdk/src/matrix instead",
},
{
name: "matrix-js-sdk/src/index",
message: "Please use matrix-js-sdk/src/matrix instead",
},
],
patterns: [
{
group: ["matrix-js-sdk/lib", "matrix-js-sdk/lib/", "matrix-js-sdk/lib/**"],
message: "Please use matrix-js-sdk/src/* instead",
},
],
},
],
},
},
],
settings: {
react: {
version: "detect",
},
},
};
function buildRestrictedPropertiesOptions(properties, message) {
return properties.map((prop) => {
let [object, property] = prop.split(".");
if (object === "*") {
object = undefined;
}
return {
object,
property,
message,
};
});
}
+1 -3
View File
@@ -1,7 +1,5 @@
{
"*": "oxfmt --no-error-on-unmatched-pattern",
"src/**/*.(ts|tsx)": ["eslint --fix"],
"scripts/**/*.(ts|tsx)": ["eslint --fix"],
"module_system/**/*.(ts|tsx)": ["eslint --fix"],
"*.{js,jsx,ts,tsx,mjs,cjs}": "oxlint --no-error-on-unmatched-pattern",
"*.pcss": ["stylelint --fix"]
}
+7
View File
@@ -1,3 +1,10 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
module.exports = {
extends: ["stylelint-config-standard"],
customSyntax: "postcss-scss",
+1 -1
View File
@@ -62,7 +62,7 @@ export class I18nWebpackPlugin {
}
const primaryPath = paths[0];
const includeLangs = [...new Set([...(await fs.readdir(primaryPath))])]
const includeLangs = [...new Set(await fs.readdir(primaryPath))]
.filter((fn) => fn.endsWith(".json"))
.map((f) => f.slice(0, -5));
+7
View File
@@ -1 +1,8 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
module.exports = "css-file-stub";
+7
View File
@@ -1,2 +1,9 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
// Yes, this is empty.
module.exports = {};
+7
View File
@@ -1 +1,8 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
module.exports = "image-file-stub";
+7
View File
@@ -1,2 +1,9 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
export const Icon = "div";
export default "image-file-stub";
+7
View File
@@ -1,3 +1,10 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
module.exports = {
sourceMaps: true,
presets: [
+3 -3
View File
@@ -134,9 +134,9 @@ function getOptionalDepNames(pkgJsonStr: string): string[] {
function findDepVersionInPackageJson(dep: string, pkgJsonStr: string): string {
const pkgJson = JSON.parse(pkgJsonStr);
const packages = {
...(pkgJson["optionalDependencies"] ?? {}),
...(pkgJson["devDependencies"] ?? {}),
...(pkgJson["dependencies"] ?? {}),
...pkgJson["optionalDependencies"],
...pkgJson["devDependencies"],
...pkgJson["dependencies"],
};
return packages[dep];
}
+1 -19
View File
@@ -24,9 +24,7 @@
"vendor:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js",
"dist": "./scripts/package.sh",
"start": "nx start",
"lint": "pnpm lint:types && pnpm lint:js && pnpm lint:style",
"lint:js": "eslint --max-warnings 0 src test playwright module_system",
"lint:js-fix": "eslint --fix src test playwright module_system",
"lint": "pnpm lint:types && pnpm lint:style",
"lint:types": "nx lint:types",
"lint:style": "stylelint \"res/css/**/*.pcss\"",
"test": "nx test:unit",
@@ -109,8 +107,6 @@
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/eslint-parser": "^7.12.10",
"@babel/eslint-plugin": "^7.12.10",
"@babel/plugin-proposal-decorators": "^7.25.9",
"@babel/plugin-proposal-export-default-from": "^7.12.1",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
@@ -134,7 +130,6 @@
"@playwright/test": "catalog:",
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
"@sentry/webpack-plugin": "^5.0.0",
"@stylistic/eslint-plugin": "^5.0.0",
"@svgr/webpack": "^8.0.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.4.8",
@@ -164,8 +159,6 @@
"@types/semver": "^7.5.8",
"@types/tar-js": "^0.3.5",
"@types/ua-parser-js": "^0.7.36",
"@typescript-eslint/eslint-plugin": "^8.19.0",
"@typescript-eslint/parser": "^8.19.0",
"@vitest/spy": "catalog:",
"babel-jest": "^30.0.0",
"babel-loader": "^10.0.0",
@@ -175,17 +168,6 @@
"css-loader": "^7.0.0",
"css-minimizer-webpack-plugin": "^8.0.0",
"dotenv": "^17.0.0",
"eslint": "8.57.1",
"eslint-config-google": "^0.14.0",
"eslint-plugin-deprecate": "0.9.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^29.0.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-matrix-org": "^3.0.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124",
"eslint-plugin-react-hooks": "^7.0.0",
"eslint-plugin-unicorn": "^56.0.0",
"express": "^5.0.0",
"fake-indexeddb": "^6.0.0",
"file-loader": "^6.0.0",
@@ -52,7 +52,7 @@ test.describe("Custom Component API", () => {
async ({ page, room, app }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, "Simple message");
await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot(
await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot(
"custom-component-tile.png",
screenshotOptions(page),
);
@@ -64,7 +64,7 @@ test.describe("Custom Component API", () => {
async ({ page, room, app }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, "Fall through here");
await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot(
await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot(
"custom-component-tile-fall-through.png",
screenshotOptions(page),
);
@@ -76,7 +76,7 @@ test.describe("Custom Component API", () => {
async ({ page, room, app }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, "Do not replace me");
await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot(
await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot(
"custom-component-tile-original.png",
screenshotOptions(page),
);
@@ -87,7 +87,7 @@ test.describe("Custom Component API", () => {
await app.client.sendMessage(room.roomId, "Do not show edits");
await page.getByText("Do not show edits").hover();
await expect(
await page.getByRole("toolbar", { name: "Message Actions" }).getByRole("button", { name: "Edit" }),
page.getByRole("toolbar", { name: "Message Actions" }).getByRole("button", { name: "Edit" }),
).not.toBeVisible();
});
test("should disallow downloading media when the allowDownloading hint is set to false", async ({
@@ -148,7 +148,7 @@ test.describe("Custom Component API", () => {
async ({ page, room, app }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, "Crash the filter!");
await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot(
await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot(
"custom-component-crash-handle-filter.png",
screenshotOptions(page),
);
@@ -160,7 +160,7 @@ test.describe("Custom Component API", () => {
async ({ page, room, app }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, "Crash the renderer!");
await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot(
await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot(
"custom-component-crash-handle-renderer.png",
screenshotOptions(page),
);
@@ -193,7 +193,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
await page.getByRole("button", { name: "React" }).click();
await page.locator(".mx_EmojiPicker_body").getByText("😀").first().click();
await page.locator(".mx_ThreadPanel").getByLabel("Mae reacted with 😀").click();
await expect(await page.locator(".mx_ThreadPanel").getByLabel("Mae reacted with 😀")).not.toBeVisible();
await expect(page.locator(".mx_ThreadPanel").getByLabel("Mae reacted with 😀")).not.toBeVisible();
});
});
});
@@ -75,7 +75,7 @@ test.describe("RightPanel", () => {
test("should have padding under leave room", { tag: "@screenshot" }, async ({ page, app }) => {
await viewRoomSummaryByName(page, app, ROOM_NAME);
const leaveButton = await page.getByRole("menuitem", { name: "Leave Room" });
const leaveButton = page.getByRole("menuitem", { name: "Leave Room" });
await leaveButton.scrollIntoViewIfNeeded();
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("with-leave-room.png");
@@ -177,7 +177,7 @@ test.describe("RightPanel", () => {
await viewRoomSummaryByName(page, app, ROOM_NAME);
await page.getByRole("menuitem", { name: "Report room" }).click();
const dialog = await page.getByRole("dialog", { name: "Report Room" });
const dialog = page.getByRole("dialog", { name: "Report Room" });
await dialog.getByLabel("reason").fill("This room should be reported");
await expect(dialog).toMatchScreenshot("room-report-dialog.png");
await dialog.getByRole("button", { name: "Send report" }).click();
@@ -189,7 +189,7 @@ test.describe("RightPanel", () => {
await viewRoomSummaryByName(page, app, ROOM_NAME);
await page.getByRole("menuitem", { name: "Report room" }).click();
const dialog = await page.getByRole("dialog", { name: "Report room" });
const dialog = page.getByRole("dialog", { name: "Report room" });
await dialog.getByRole("switch", { name: "Leave room" }).click();
await dialog.getByLabel("reason").fill("This room should be reported");
await dialog.getByRole("button", { name: "Send report" }).click();
@@ -38,7 +38,7 @@ test.describe("Roles & Permissions room settings tab", () => {
await combobox.selectOption("Moderator");
// Should display a modal to warn that we are demoting the only admin user
const modal = await page.locator(".mx_Dialog", {
const modal = page.locator(".mx_Dialog", {
hasText: "Warning",
});
await expect(modal).toBeVisible();
@@ -126,7 +126,7 @@ test.describe("Roles & Permissions room settings tab", () => {
// Element should have automatically set the room to "sharing" history visibility
const errorDialog = page.getByRole("heading", { name: "Cannot make room private" });
await expect(errorDialog).toBeVisible();
await errorDialog.getByLabel("OK");
errorDialog.getByLabel("OK");
await expect(settingsGroupHistory.getByText("Anyone")).toBeChecked();
},
);
@@ -30,6 +30,6 @@ test.describe("share from URL", () => {
const lastMessage = page.locator(".mx_RoomView_MessageList .mx_EventTile_last");
await expect(lastMessage).toBeVisible();
const lastMessageText = await lastMessage.locator(".mx_EventTile_body").innerText();
await expect(lastMessageText).toBe("Hello world");
expect(lastMessageText).toBe("Hello world");
});
});
@@ -71,7 +71,7 @@ const sendImage = async (
): Promise<ISendEventResponse> => {
const upload = await client.uploadContent(pngBytes, { name: "image.png", type: "image/png" });
return client.sendEvent(roomId, null, "m.room.message" as EventType, {
...(additionalContent ?? {}),
...additionalContent,
msgtype: "m.image" as MsgType,
body: "image.png",
@@ -153,7 +153,7 @@ test.describe("Element Call", () => {
await page.getByRole("menuitem", { name: "Element Call" }).click();
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
// Ensure we set the correct parameters for ECall.
const url = new URL(frameUrlStr);
const hash = new URLSearchParams(url.hash.slice(1));
@@ -179,7 +179,7 @@ test.describe("Element Call", () => {
await page.keyboard.up("Shift");
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
const url = new URL(frameUrlStr);
const hash = new URLSearchParams(url.hash.slice(1));
assertCommonCallParameters(url.searchParams, hash, user, room);
@@ -204,7 +204,7 @@ test.describe("Element Call", () => {
// And test joining
await button.click();
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
const url = new URL(frameUrlStr);
const hash = new URLSearchParams(url.hash.slice(1));
assertCommonCallParameters(url.searchParams, hash, user, room);
@@ -254,7 +254,7 @@ test.describe("Element Call", () => {
// And test joining
await button.click();
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
const url = new URL(frameUrlStr);
const hash = new URLSearchParams(url.hash.slice(1));
assertCommonCallParameters(url.searchParams, hash, user, room);
@@ -283,7 +283,7 @@ test.describe("Element Call", () => {
// And test joining
await button.click();
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
const url = new URL(frameUrlStr);
const hash = new URLSearchParams(url.hash.slice(1));
assertCommonCallParameters(url.searchParams, hash, user, room);
@@ -317,7 +317,7 @@ test.describe("Element Call", () => {
await page.getByRole("menuitem", { name: "Element Call" }).click();
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
const url = new URL(frameUrlStr);
const hash = new URLSearchParams(url.hash.slice(1));
assertCommonCallParameters(url.searchParams, hash, user, room);
@@ -335,7 +335,7 @@ test.describe("Element Call", () => {
await page.keyboard.up("Shift");
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
const url = new URL(frameUrlStr);
const hash = new URLSearchParams(url.hash.slice(1));
assertCommonCallParameters(url.searchParams, hash, user, room);
@@ -351,7 +351,7 @@ test.describe("Element Call", () => {
await page.getByRole("menuitem", { name: "Element Call" }).click();
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
// The call should be presented in the picture-in-picture container, right in the room we started it
// from, rather than taking over the room view.
@@ -369,7 +369,7 @@ test.describe("Element Call", () => {
// And test joining
await button.click();
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
const url = new URL(frameUrlStr);
const hash = new URLSearchParams(url.hash.slice(1));
assertCommonCallParameters(url.searchParams, hash, user, room);
@@ -409,7 +409,7 @@ test.describe("Element Call", () => {
// And test joining
await button.click();
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
const url = new URL(frameUrlStr);
const hash = new URLSearchParams(url.hash.slice(1));
assertCommonCallParameters(url.searchParams, hash, user, room);
@@ -443,7 +443,7 @@ test.describe("Element Call", () => {
// And test joining
await button.click();
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
const url = new URL(frameUrlStr);
const hash = new URLSearchParams(url.hash.slice(1));
assertCommonCallParameters(url.searchParams, hash, user, room);
@@ -475,7 +475,7 @@ test.describe("Element Call", () => {
const roomId = new URL(page.url()).hash.slice("#/room/".length);
const frameUrlStr = await page.locator("iframe").getAttribute("src");
await expect(frameUrlStr).toBeDefined();
expect(frameUrlStr).toBeDefined();
// Ensure we set the correct parameters for ECall.
const url = new URL(frameUrlStr);
const hash = new URLSearchParams(url.hash.slice(1));
@@ -518,7 +518,7 @@ test.describe("Element Call", () => {
await expect(callFrame.getByText("In call", { exact: true })).toBeVisible();
// Wait for Element Web to pickup the RTC session and update the room list entry.
await expect(await page.getByTestId("notification-decoration")).toBeVisible();
await expect(page.getByTestId("notification-decoration")).toBeVisible();
}
test("should be able to switch rooms and have the call persist", async ({ page, user, room, app }) => {
@@ -558,9 +558,9 @@ test.describe("Element Call", () => {
await expect(pipContainer).not.toBeVisible();
// Wait for call to stop.
await expect(await page.getByTestId("notification-decoration")).not.toBeVisible();
await expect(page.getByTestId("notification-decoration")).not.toBeVisible();
await app.viewRoomById(room.roomId);
await expect(await page.getByTestId("join-call-button")).not.toBeVisible();
await expect(page.getByTestId("join-call-button")).not.toBeVisible();
// Join the call again.
await openAndJoinCall(page);
@@ -590,8 +590,8 @@ test.describe("Element Call", () => {
await expect(pipContainer).not.toBeVisible();
// Wait for call to stop.
await expect(await page.getByTestId("notification-decoration")).not.toBeVisible();
await expect(await page.getByTestId("join-call-button")).not.toBeVisible();
await expect(page.getByTestId("notification-decoration")).not.toBeVisible();
await expect(page.getByTestId("join-call-button")).not.toBeVisible();
// Join the call again, but from the other room.
await openAndJoinCall(page);
+6 -6
View File
@@ -202,7 +202,7 @@ export class Client {
/**
* Create a room with given options.
* @param options the options to apply when creating the room
* @return the ID of the newly created room
* @returns the ID of the newly created room
*/
public async createRoom(options: ICreateRoomOpts): Promise<string> {
const client = await this.prepareClient();
@@ -217,7 +217,7 @@ export class Client {
/**
* Create a space with given options.
* @param options the options to apply when creating the space
* @return the ID of the newly created space (room)
* @returns the ID of the newly created space (room)
*/
public async createSpace(options: ICreateRoomOpts): Promise<string> {
return this.createRoom({
@@ -412,8 +412,8 @@ export class Client {
/**
* @param {string} name
* @param {module:client.callback} callback Optional.
* @return {Promise} Resolves: {} an empty object.
* @return {module:http-api.MatrixError} Rejects: with an error response.
* @returns {Promise} Resolves: {} an empty object.
* @returns {module:http-api.MatrixError} Rejects: with an error response.
*/
public async setDisplayName(name: string): Promise<EmptyObject> {
const client = await this.prepareClient();
@@ -423,8 +423,8 @@ export class Client {
/**
* @param {string} url
* @param {module:client.callback} callback Optional.
* @return {Promise} Resolves: {} an empty object.
* @return {module:http-api.MatrixError} Rejects: with an error response.
* @returns {Promise} Resolves: {} an empty object.
* @returns {module:http-api.MatrixError} Rejects: with an error response.
*/
public async setAvatarUrl(url: string): Promise<EmptyObject> {
const client = await this.prepareClient();
+1 -1
View File
@@ -41,7 +41,7 @@ export class Settings {
* null.
* @param {SettingLevel} level The level to change the value at.
* @param {*} value The new value of the setting, may be null.
* @return {Promise} Resolves when the setting has been changed.
* @returns {Promise} Resolves when the setting has been changed.
*/
public async setValue(settingName: string, roomId: string | null, level: SettingLevel, value: any): Promise<void> {
return this.page.evaluate<
+2 -1
View File
@@ -10,7 +10,8 @@
"allowImportingTsExtensions": true,
"strictNullChecks": false,
"noImplicitAny": false,
"types": ["node"]
"types": ["node"],
"noEmit": true
},
"include": [
"**/*.ts",
+7
View File
@@ -1,3 +1,10 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
/*
* Quick-n-dirty algebraic datatypes.
*
+8 -1
View File
@@ -1,3 +1,10 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
class StartupError extends Error {}
/*
@@ -165,7 +172,7 @@ function BundlePicker() {
setBundle(name);
}
}, console.log.bind(console));
}, [baseUrl]);
}, [baseUrl, bundle]);
/* ------------------------- */
/* Follow user state changes */
+2 -2
View File
@@ -70,8 +70,8 @@ declare global {
// so we don't accidentally use the methods on NodeJS.Timeout - they only exist in a subset of environments.
// The overload for clear{Interval,Timeout} is resolved as expected.
// We use `ReturnType<typeof setTimeout>` in the code to be agnostic of if this definition gets loaded.
function setInterval(handler: TimerHandler, timeout: number, ...arguments: any[]): number;
function setTimeout(handler: TimerHandler, timeout: number, ...arguments: any[]): number;
function setInterval(handler: TimerHandler, timeout: number, ...args: any[]): number;
function setTimeout(handler: TimerHandler, timeout: number, ...args: any[]): number;
interface Window {
mxSendRageshake: (text: string, withLogs?: boolean) => Promise<void>;
+7 -7
View File
@@ -69,7 +69,7 @@ export default class AddThreepid {
* Attempt to add an email threepid to the homeserver.
* This will trigger a side-effect of sending an email to the provided email address.
* @param {string} emailAddress The email address to add
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
* @returns {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
*/
public async addEmailAddress(emailAddress: string): Promise<IRequestTokenResponse> {
try {
@@ -91,7 +91,7 @@ export default class AddThreepid {
* Attempt to bind an email threepid on the identity server via the homeserver.
* This will trigger a side-effect of sending an email to the provided email address.
* @param {string} emailAddress The email address to add
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
* @returns {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
*/
public async bindEmailAddress(emailAddress: string): Promise<IRequestTokenResponse> {
this.bind = true;
@@ -122,7 +122,7 @@ export default class AddThreepid {
* This will trigger a side-effect of sending an SMS to the provided phone number.
* @param {string} phoneCountry The ISO 2 letter code of the country to resolve phoneNumber in
* @param {string} phoneNumber The national or international formatted phone number to add
* @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
* @returns {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
*
* @throws {UserFriendlyError} An appropriate user-friendly error if the verification code could not be sent.
*/
@@ -155,7 +155,7 @@ export default class AddThreepid {
* This will trigger a side-effect of sending an SMS to the provided phone number.
* @param {string} phoneCountry The ISO 2 letter code of the country to resolve phoneNumber in
* @param {string} phoneNumber The national or international formatted phone number to add
* @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
* @returns {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
*/
public async bindMsisdn(phoneCountry: string, phoneNumber: string): Promise<IRequestMsisdnTokenResponse> {
this.bind = true;
@@ -184,7 +184,7 @@ export default class AddThreepid {
/**
* Checks if the email link has been clicked by attempting to add the threepid
* @return {Promise} Resolves if the email address was added. Rejects with an object
* @returns {Promise} Resolves if the email address was added. Rejects with an object
* with a "message" property which contains a human-readable message detailing why
* the request failed.
*/
@@ -254,7 +254,7 @@ export default class AddThreepid {
/**
* @param {{type: string, session?: string}} auth UI auth object
* @return {Promise<Object>} Response from /3pid/add call (in current spec, an empty object)
* @returns {Promise<Object>} Response from /3pid/add call (in current spec, an empty object)
*/
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
private makeAddThreepidOnlyRequest = (auth?: IAddThreePidOnlyBody["auth"] | null): Promise<{}> => {
@@ -269,7 +269,7 @@ export default class AddThreepid {
* Takes a phone number verification code as entered by the user and validates
* it with the identity server, then if successful, adds the phone number.
* @param {string} msisdnToken phone number verification code as entered by the user
* @return {Promise} Resolves if the phone number was added. Rejects with an object
* @returns {Promise} Resolves if the phone number was added. Rejects with an object
* with a "message" property which contains a human-readable message detailing why
* the request failed.
*/
+1 -1
View File
@@ -137,7 +137,7 @@ export function defaultAvatarUrlForString(s: string): string {
* returns the first (non-sigil) character of 'name',
* converted to uppercase
* @param {string} name
* @return {string} the first letter
* @returns {string} the first letter
*/
export function getInitialLetter(name: string): string | undefined {
if (!name) {
+1 -1
View File
@@ -279,7 +279,7 @@ export default abstract class BasePlatform {
/**
* Get our platform specific EventIndexManager.
*
* @return {BaseEventIndexManager} The EventIndex manager for our platform,
* @returns {BaseEventIndexManager} The EventIndex manager for our platform,
* can be null if the platform doesn't support event indexing.
*/
public getEventIndexingManager(): BaseEventIndexManager | null {
+8 -8
View File
@@ -77,7 +77,7 @@ interface IMediaConfig {
* Load a file into a newly created image element.
*
* @param {File} imageFile The file to load in an image element.
* @return {Promise} A promise that resolves with the html image element.
* @returns {Promise} A promise that resolves with the html image element.
*/
async function loadImageElement(imageFile: File): Promise<{
width: number;
@@ -148,7 +148,7 @@ const ALWAYS_INCLUDE_THUMBNAIL = ["image/avif", "image/webp", "image/svg+xml"];
* @param {MatrixClient} matrixClient A matrixClient to upload the thumbnail with.
* @param {String} roomId The ID of the room the image will be uploaded in.
* @param {File} imageFile The image to read and thumbnail.
* @return {Promise} A promise that resolves with the attachment info.
* @returns {Promise} A promise that resolves with the attachment info.
*/
async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imageFile: File): Promise<ImageInfo> {
let thumbnailType = "image/png";
@@ -196,7 +196,7 @@ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imag
* Load a file into a newly created audio element and load the metadata
*
* @param {File} audioFile The file to load in an audio element.
* @return {Promise} A promise that resolves with the audio element.
* @returns {Promise} A promise that resolves with the audio element.
*/
function loadAudioElement(audioFile: File): Promise<HTMLAudioElement> {
return new Promise((resolve, reject) => {
@@ -228,7 +228,7 @@ function loadAudioElement(audioFile: File): Promise<HTMLAudioElement> {
* Read the metadata for an audio file.
*
* @param {File} audioFile The audio to read.
* @return {Promise} A promise that resolves with the attachment info.
* @returns {Promise} A promise that resolves with the attachment info.
*/
async function infoForAudioFile(audioFile: File): Promise<AudioInfo> {
const audio = await loadAudioElement(audioFile);
@@ -240,7 +240,7 @@ async function infoForAudioFile(audioFile: File): Promise<AudioInfo> {
* in an attempt to guarantee the first frame will be showing.
*
* @param {File} videoFile The file to load in a video element.
* @return {Promise} A promise that resolves with the video element.
* @returns {Promise} A promise that resolves with the video element.
*/
function loadVideoElement(videoFile: File): Promise<HTMLVideoElement> {
return new Promise((resolve, reject) => {
@@ -286,7 +286,7 @@ function loadVideoElement(videoFile: File): Promise<HTMLVideoElement> {
* @param {MatrixClient} matrixClient A matrixClient to upload the thumbnail with.
* @param {String} roomId The ID of the room the video will be uploaded to.
* @param {File} videoFile The video to read and thumbnail.
* @return {Promise} A promise that resolves with the attachment info.
* @returns {Promise} A promise that resolves with the attachment info.
*/
function infoForVideoFile(matrixClient: MatrixClient, roomId: string, videoFile: File): Promise<VideoInfo> {
const thumbnailType = "image/jpeg";
@@ -311,7 +311,7 @@ function infoForVideoFile(matrixClient: MatrixClient, roomId: string, videoFile:
/**
* Read the file as an ArrayBuffer.
* @param {File} file The file to read
* @return {Promise} A promise that resolves with an ArrayBuffer when the file
* @returns {Promise} A promise that resolves with an ArrayBuffer when the file
* is read.
*/
function readFileAsArrayBuffer(file: File | Blob): Promise<ArrayBuffer> {
@@ -337,7 +337,7 @@ function readFileAsArrayBuffer(file: File | Blob): Promise<ArrayBuffer> {
* @param {Function?} progressHandler optional callback to be called when a chunk of
* data is uploaded.
* @param {AbortController?} controller optional abortController to use for this upload.
* @return {Promise} A promise that resolves with an object.
* @returns {Promise} A promise that resolves with an object.
* If the file is unencrypted then the object will have a "url" key.
* If the file is encrypted then the object will have a "file" key.
*/
+3 -3
View File
@@ -79,7 +79,7 @@ function mightContainEmoji(str?: string): boolean {
* Returns the shortcode for an emoji character.
*
* @param {String} char The emoji character
* @return {String} The shortcode (such as :thumbup:)
* @returns {String} The shortcode (such as :thumbup:)
*/
export function unicodeToShortcode(char: string): string {
const shortcodes = getEmojiFromUnicode(char)?.shortcodes;
@@ -119,7 +119,7 @@ export function getHtmlText(insaneHtml: string): string {
* Note that the HTML sanitiser library has its own internal logic for
* doing this, to which we pass the same list of schemes. This is used in
* other places we need to sanitise URLs.
* @return true if permitted, otherwise false
* @returns true if permitted, otherwise false
*/
export function isUrlPermitted(inputUrl: string): boolean {
try {
@@ -497,7 +497,7 @@ export function bodyToHtml(content: IContent, highlights?: string[], opts: Event
* @param htmlTopic optional html topic
* @param ref React ref to attach to any React components returned
* @param allowExtendedHtml whether to allow extended HTML tags such as headings and lists
* @return The HTML-ified node.
* @returns The HTML-ified node.
*/
export function topicToHtml(
topic?: string,
+1 -1
View File
@@ -54,7 +54,7 @@ export default class MediaDeviceHandler extends EventEmitter {
* (https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API)
* is ready for primetime, it might help make this simpler.
*
* @return Promise<IMediaDevices> The available media devices
* @returns Promise<IMediaDevices> The available media devices
*/
public static async getDevices(): Promise<IMediaDevices | undefined> {
try {
+2 -2
View File
@@ -185,7 +185,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
* caused a chunk of tests to fail, so for now they continue to use this.
*
* @param reason either "backgroundClick" or undefined
* @return whether a modal was closed
* @returns whether a modal was closed
*/
public closeCurrentModal(reason?: ModalCloseReason): boolean {
const modal = this.getCurrentModal();
@@ -297,7 +297,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
/**
* @callback onBeforeClose
* @param {string?} reason either "backgroundClick" or null
* @return {Promise<bool>} whether the dialog should close
* @returns {Promise<bool>} whether the dialog should close
*/
/**
+1
View File
@@ -393,6 +393,7 @@ export default class Notifier extends TypedEventEmitter<keyof EmittedEvents, Emi
return;
}
// oxlint-disable-next-line promise/no-callback-in-promise
if (callback) callback();
this.sdkContext.posthogAnalytics.trackEvent<PermissionChangedEvent>({
+1 -1
View File
@@ -73,7 +73,7 @@ export default class PasswordReset {
/**
* Checks if the email link has been clicked by attempting to change the password
* for the mxid linked to the email.
* @return {Promise} Resolves if the password was reset. Rejects with an object
* @returns {Promise} Resolves if the password was reset. Rejects with an object
* with a "message" property which contains a human-readable message detailing why
* the reset failed, e.g. "There is no mapped matrix user ID for the given email address".
*/
+1 -1
View File
@@ -127,7 +127,7 @@ describe("PosthogAnalytics", () => {
it("Should not track events if anonymous", async () => {
analytics.setAnonymity(Anonymity.Anonymous);
await analytics.trackEvent<ITestEvent>({
analytics.trackEvent<ITestEvent>({
eventName: "TestEvents",
foo: "bar",
});
+1 -1
View File
@@ -227,7 +227,7 @@ export default class ScalarAuthClient {
* This can be useful to temporarily prevent purchased assets from being displayed.
* @param {WidgetType} widgetType The Widget Type to disable assets for
* @param {string} widgetId The widget ID to disable assets for
* @return {Promise} Resolves on completion
* @returns {Promise} Resolves on completion
*/
public async disableWidgetAssets(widgetType: WidgetType, widgetId: string): Promise<void> {
const url = new URL(this.getStarterLink(this.apiUrl + "/widgets/set_assets_state"));
+2 -2
View File
@@ -412,7 +412,7 @@ function combineEventSources(
* index.
* @param {object} serverEvents An unprocessed search result from the server.
*
* @return {object} A response object that combines the events from the
* @returns {object} A response object that combines the events from the
* different event sources.
*
*/
@@ -476,7 +476,7 @@ function combineEvents(
* index.
* @param {object} serverEvents An unprocessed search result from the server.
*
* @return {object} A response object that combines the events from the
* @returns {object} A response object that combines the events from the
* different event sources.
*/
function combineResponses(
+3 -3
View File
@@ -253,9 +253,9 @@ export class SlidingSyncManager {
try {
// if we only have range changes then call a different function so we don't nuke the list from before
if (updateArgs.ranges && Object.keys(updateArgs).length === 1) {
await this.slidingSync!.setListRanges(listKey, updateArgs.ranges);
this.slidingSync!.setListRanges(listKey, updateArgs.ranges);
} else {
await this.slidingSync!.setList(listKey, list);
this.slidingSync!.setList(listKey, list);
}
} catch (err) {
logger.debug("ensureListRegistered: update failed txn_id=", err);
@@ -391,7 +391,7 @@ export class SlidingSyncManager {
/**
* Check if the server "natively" supports sliding sync (with an unstable endpoint).
* @param client The MatrixClient to use
* @return Whether the "native" (unstable) endpoint is supported
* @returns Whether the "native" (unstable) endpoint is supported
*/
public async nativeSlidingSyncSupport(client: MatrixClient): Promise<boolean> {
// Per https://github.com/matrix-org/matrix-spec-proposals/pull/3575/files#r1589542561
-1
View File
@@ -22,7 +22,6 @@ export function getUserTimezone(): string | undefined {
/**
* Set in the settings the given timezone
* @timezone
*/
export function setUserTimezone(timezone: string): Promise<void> {
return SettingsStore.setValue(USER_TIMEZONE_KEY, null, SettingLevel.DEVICE, timezone);
+1 -1
View File
@@ -299,7 +299,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
// Finally do our post-processing and clean up
this.recording = false;
await this.recorder!.close();
this.recorder!.close();
this.emit(RecordingState.Ended);
});
}
@@ -58,7 +58,7 @@ export default abstract class AutocompleteProvider {
* @param {string} query The query string
* @param {ISelectionRange} selection Selection to search
* @param {boolean} force True if the user is forcing completion
* @return {object} { command, range } where both objects fields are null if no match
* @returns {object} { command, range } where both objects fields are null if no match
*/
public getCurrentCommand(query: string, selection: ISelectionRange, force = false): Partial<ICommand> {
let commandRegex = this.commandRegex;
@@ -6,8 +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.
*/
import React, { type JSX } from "react";
import { useContext, useState } from "react";
import React, { type JSX, useContext, useState } from "react";
import { ChatSolidIcon, ExploreIcon, GroupIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { AutoHideScrollbar } from "@element-hq/web-shared-components";
@@ -311,7 +311,7 @@ class LoggedInView extends React.Component<IProps, IState> {
private calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit): void {
const error = (syncError?.error as MatrixError)?.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
if (error) {
usageLimitEventContent = (syncError?.error as MatrixError).data as IUsageLimit;
usageLimitEventContent = (syncError?.error as MatrixError)?.data as IUsageLimit;
}
// usageLimitDismissed is true when the user has explicitly hidden the toast
@@ -691,7 +691,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
break;
case "logout":
this.stores.legacyCallHandler.hangupAllCalls();
Promise.all([...[...CallStore.instance.connectedCalls].map((call) => call.disconnect())]).finally(() =>
Promise.all([...CallStore.instance.connectedCalls].map((call) => call.disconnect())).finally(() =>
Lifecycle.logout(),
);
break;
@@ -25,7 +25,6 @@ interface ReleaseAnnouncementProps extends Omit<
* @param feature - the feature to announce, should be listed in {@link Feature}
* @param children
* @param props
* @constructor
*/
export function ReleaseAnnouncement({
feature,
@@ -366,7 +366,7 @@ interface ILocalRoomCreateLoaderProps {
* Room create loader view displaying a message and a spinner.
*
* @param {ILocalRoomCreateLoaderProps} props Room view props
* @return {ReactElement}
* @returns {ReactElement}
*/
function LocalRoomCreateLoader(props: ILocalRoomCreateLoaderProps): ReactElement {
const text = _t("room|creating_room_text", { names: props.names });
@@ -798,7 +798,7 @@ const SpaceHierarchy: React.FC<IProps> = ({ space, initialText = "", showRoom, a
// Walk back up the tree to find all parents of the direct matches to show their place in the hierarchy
const visited = new Set<string>();
const queue = [...directMatches.map((r) => r.room_id)];
const queue = directMatches.map((r) => r.room_id);
while (queue.length) {
const roomId = queue.pop()!;
visited.add(roomId);
@@ -1728,7 +1728,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
* have been sent by the server, not
* implicit ones generated by the JS
* SDK.
* @return {String} the event ID
* @returns {String} the event ID
*/
private getCurrentReadReceipt(ignoreSynthesized = false): string | null {
const client = MatrixClientPeg.get();
@@ -1886,7 +1886,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
* Iterate across all of the timelineSets and timelines inside to expose all of
* the event IDs contained inside.
*
* @return An event ID list for every timeline in every timelineSet
* @returns An event ID list for every timeline in every timelineSet
*/
function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): { [key: string]: string[] }[] {
const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => {
@@ -116,6 +116,7 @@ export default class ViewSource extends React.Component<IProps, IState> {
return (
<MatrixClientContext.Consumer>
{(cli) => (
// oxlint-disable-next-line react/jsx-no-constructed-context-values
<DevtoolsContext.Provider value={{ room: cli.getRoom(roomId)! }}>
<StateEventEditor onBack={this.onBack} mxEvent={mxEvent} />
</DevtoolsContext.Provider>
@@ -127,6 +128,7 @@ export default class ViewSource extends React.Component<IProps, IState> {
return (
<MatrixClientContext.Consumer>
{(cli) => (
// oxlint-disable-next-line react/jsx-no-constructed-context-values
<DevtoolsContext.Provider value={{ room: cli.getRoom(roomId)! }}>
<TimelineEventEditor onBack={this.onBack} mxEvent={mxEvent} />
</DevtoolsContext.Provider>
@@ -108,7 +108,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
const loginViews = flows.map((f) => STATIC_FLOWS_TO_VIEWS[f.type]);
const isSocialSignOn = loginViews.includes(LoginView.Password) && loginViews.includes(LoginView.SSO);
const firstView = loginViews.filter((f) => !!f)[0] || LoginView.Unsupported;
const firstView = loginViews.find((f) => !!f) || LoginView.Unsupported;
const chosenView = isSocialSignOn ? LoginView.PasswordWithSocialSignOn : firstView;
this.setState({ flows, loginView: chosenView });
}
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import { isEqual } from "lodash";
import React, { type JSX, type ComponentProps, type PropsWithChildren, type Reducer, useReducer } from "react";
import React, { type JSX, type ComponentProps, type PropsWithChildren, type Reducer, useReducer, useMemo } from "react";
import { AuthHeaderContext } from "./AuthHeaderContext";
import { type AuthHeaderModifier } from "./AuthHeaderModifier";
@@ -36,5 +36,6 @@ export function AuthHeaderProvider({ children }: PropsWithChildren): JSX.Element
},
[] as ComponentProps<typeof AuthHeaderModifier>[],
);
return <AuthHeaderContext.Provider value={{ state, dispatch }}>{children}</AuthHeaderContext.Provider>;
const context = useMemo(() => ({ state, dispatch }), [state, dispatch]);
return <AuthHeaderContext.Provider value={context}>{children}</AuthHeaderContext.Provider>;
}
@@ -29,7 +29,7 @@ interface KeyStoragePanelState {
* A function that can be called to enable or disable key storage.
* @param enable True to turn key storage on or false to turn it off
*/
setEnabled: (enable: boolean) => void;
setEnabled: (enable: boolean) => Promise<void>;
/**
* True if the state is still loading for the first time
@@ -151,6 +151,7 @@ const DevtoolsDialog: React.FC<IProps> = ({ roomId, threadRootId, onFinished })
)}
<div className="mx_DevTools_label_bottom" />
{cli.getRoom(roomId) && (
// oxlint-disable-next-line react/jsx-no-constructed-context-values
<DevtoolsContext.Provider value={{ room: cli.getRoom(roomId)!, threadRootId }}>
{body}
</DevtoolsContext.Provider>
@@ -30,7 +30,7 @@ const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
subheading,
children,
rageshakeLabel,
rageshakeData = {},
rageshakeData,
onFinished,
}) => {
const [comment, setComment] = useState("");
@@ -1390,7 +1390,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
new Tab(
TabId.UserDirectory,
_td("invite|transfer_user_directory_tab"),
<UserProfileSolidIcon />,
<UserProfileSolidIcon key={TabId.UserDirectory} />,
usersSection,
),
];
@@ -78,7 +78,7 @@ const addAllParents = (set: Set<Room>, room: Room): void => {
});
};
const ManageRestrictedJoinRuleDialog: React.FC<IProps> = ({ room, selected = [], onFinished }) => {
const ManageRestrictedJoinRuleDialog: React.FC<IProps> = ({ room, selected, onFinished }) => {
const cli = room.client;
const [newSelected, setNewSelected] = useState(new Set<string>(selected));
const [query, setQuery] = useState("");
@@ -92,7 +92,7 @@ const ManageRestrictedJoinRuleDialog: React.FC<IProps> = ({ room, selected = [],
Array.from(parents),
SpaceStore.instance.spacePanelSpaces.filter((s) => !parents.has(s)),
filterBoolean(
selected.map((roomId) => {
selected?.map((roomId) => {
const room = cli.getRoom(roomId);
if (!room) {
return { roomId, name: roomId } as Room;
@@ -100,7 +100,7 @@ const ManageRestrictedJoinRuleDialog: React.FC<IProps> = ({ room, selected = [],
if (room.getMyMembership() !== KnownMembership.Join || !room.isSpaceRoom()) {
return room;
}
}),
}) ?? [],
),
];
}, [cli, selected, room]);
@@ -65,8 +65,8 @@ const SpacePreferencesDialog: React.FC<IProps> = ({ space, onFinished }) => {
new Tab(
SpacePreferenceTab.Appearance,
_td("common|appearance"),
<VisibilityOnIcon />,
<SpacePreferencesAppearanceTab space={space} />,
<VisibilityOnIcon key={SpacePreferenceTab.Appearance} />,
<SpacePreferencesAppearanceTab key={SpacePreferenceTab.Appearance} space={space} />,
),
];
@@ -54,27 +54,36 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
new Tab(
SpaceSettingsTab.General,
_td("common|general"),
<SettingsSolidIcon />,
<SpaceSettingsGeneralTab matrixClient={cli} space={space} />,
<SettingsSolidIcon key={SpaceSettingsTab.General} />,
<SpaceSettingsGeneralTab key={SpaceSettingsTab.General} matrixClient={cli} space={space} />,
),
new Tab(
SpaceSettingsTab.Visibility,
_td("room_settings|visibility|title"),
<VisibilityOnIcon />,
<SpaceSettingsVisibilityTab matrixClient={cli} space={space} closeSettingsFn={onFinished} />,
<VisibilityOnIcon key={SpaceSettingsTab.Visibility} />,
<SpaceSettingsVisibilityTab
key={SpaceSettingsTab.Visibility}
matrixClient={cli}
space={space}
closeSettingsFn={onFinished}
/>,
),
new Tab(
SpaceSettingsTab.Roles,
_td("room_settings|permissions|title"),
<AdminIcon />,
<RolesRoomSettingsTab room={space} />,
<AdminIcon key={SpaceSettingsTab.Roles} />,
<RolesRoomSettingsTab key={SpaceSettingsTab.Roles} room={space} />,
),
SettingsStore.getValue(UIFeature.AdvancedSettings)
? new Tab(
SpaceSettingsTab.Advanced,
_td("common|advanced"),
<AdvancedSettingsIcon />,
<AdvancedRoomSettingsTab room={space} closeSettingsFn={onFinished} />,
<AdvancedSettingsIcon key={SpaceSettingsTab.Advanced} />,
<AdvancedRoomSettingsTab
key={SpaceSettingsTab.Advanced}
room={space}
closeSettingsFn={onFinished}
/>,
)
: null,
].filter(Boolean) as NonEmptyArray<Tab<SpaceSettingsTab>>;
@@ -36,7 +36,6 @@ export default class UploadFailureDialog extends React.Component<IProps> {
public render(): React.ReactNode {
let message;
let preview;
let buttons;
if (this.props.totalFiles === 1 && this.props.badFiles.length === 1) {
message = _t(
@@ -105,10 +104,7 @@ export default class UploadFailureDialog extends React.Component<IProps> {
title={_t("upload_file|error_title")}
contentId="mx_Dialog_content"
>
<div id="mx_Dialog_content">
{message}
{preview}
</div>
<div id="mx_Dialog_content">{message}</div>
{buttons}
</BaseDialog>
@@ -300,7 +300,7 @@ export default class AppTile extends React.Component<IProps, IState> {
* Set initial component state when the App wUrl (widget URL) is being updated.
* Component props *must* be passed (rather than relying on this.props).
* @param {Object} newProps The new properties of the component
* @return {Object} Updated component state to be set with setState
* @returns {Object} Updated component state to be set with setState
*/
private getNewState(newProps: IProps): IState {
return {
@@ -43,7 +43,7 @@ const GenericEventListSummary: React.FC<IProps> = ({
threshold = 3,
onToggle,
startExpanded = false,
summaryMembers = [],
summaryMembers,
summaryText,
layout = Layout.Group,
"data-testid": testId,
@@ -83,7 +83,7 @@ const GenericEventListSummary: React.FC<IProps> = ({
);
} else {
const uniqueMembers = uniqBy(
summaryMembers.filter((member) => {
summaryMembers?.filter((member) => {
if (!member?.getMxcAvatarUrl) {
logger.error(
"EventListSummary given null summaryMember, termites may be afoot eating event senders",
@@ -12,7 +12,6 @@ import { TooltipProvider } from "@vector-im/compound-web";
import dis from "../../../dispatcher/dispatcher";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { type ActionPayload } from "../../../dispatcher/payloads";
import { SDKContext } from "../../../contexts/SDKContext.ts";
@@ -170,7 +169,7 @@ export default class PersistedElement extends React.Component<IProps> {
const content = (
<StrictMode>
<SDKContext.Provider value={this.context}>
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
<MatrixClientContext.Provider value={this.context.client!}>
<TooltipProvider>
<div ref={this.collectChild} style={this.props.style}>
{this.props.children}
@@ -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.
*/
import React, { type ReactNode, type KeyboardEvent, type Ref, type MouseEvent } from "react";
import React, { type ReactNode, type KeyboardEvent, type Ref, type MouseEvent, useMemo } from "react";
import classNames from "classnames";
import { IconButton, Text } from "@vector-im/compound-web";
import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
@@ -103,9 +103,10 @@ const BaseCard: React.FC<IProps> = ({
}
const shouldRenderHeader = header || !hideHeaderButtons;
const context = useMemo(() => ({ isCard: true }), []);
return (
<CardContext.Provider value={{ isCard: true }}>
<CardContext.Provider value={context}>
<div
id={id}
aria-labelledby={ariaLabelledBy}
@@ -96,7 +96,7 @@ describe("MessageComposerUrlPreview", () => {
{ timeout: DEBOUNCE_REQUEST_TIMEOUT_MS },
);
});
test("to reset module component override when filter function does not match ", async () => {
test("to reset module component override when filter function does not match", async () => {
const modApi = {
customComponents: new CustomComponentsApi(),
} as ModuleApi;
@@ -25,7 +25,7 @@ export const EMOTE_PREFIX = "/me ";
function attachRelation(content: IContent, relation?: IEventRelation): void {
if (relation) {
content["m.relates_to"] = {
...(content["m.relates_to"] || {}),
...content["m.relates_to"],
...relation,
};
}
@@ -40,7 +40,7 @@ export default class BridgeSettingsTab extends React.Component<IProps> {
const roomState = client.getRoom(roomId)?.currentState;
if (!roomState) return [];
return BRIDGE_EVENT_TYPES.map((typeName) => roomState.getStateEvents(typeName)).flat(1);
return BRIDGE_EVENT_TYPES.flatMap((typeName) => roomState.getStateEvents(typeName));
}
public render(): React.ReactNode {
@@ -80,7 +80,7 @@ export default class AudioFeed extends React.Component<IProps, IState> {
// should serialise the ones that need to be serialised but then be able to interrupt
// them with another load() which will cancel the pending one, but since we don't call
// load() explicitly, it shouldn't be a problem. - Dave
await element.load();
element.load();
} catch (e) {
logger.info(
`Failed to play media element with feed for userId ` +
@@ -5,7 +5,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.
*/
import React, { createContext } from "react";
import React, { createContext, useMemo } from "react";
import { useCurrentPhase } from "../hooks/right-panel/useCurrentPhase";
import { type RightPanelPhases } from "../stores/right-panel/RightPanelStorePhases";
@@ -26,9 +26,6 @@ export const CurrentRightPanelPhaseContextProvider: React.FC<React.PropsWithChil
children,
}) => {
const { currentPhase, isOpen } = useCurrentPhase(roomId);
return (
<CurrentRightPanelPhaseContext.Provider value={{ currentPhase, isPanelOpen: isOpen }}>
{children}
</CurrentRightPanelPhaseContext.Provider>
);
const context = useMemo(() => ({ currentPhase, isPanelOpen: isOpen }), [currentPhase, isOpen]);
return <CurrentRightPanelPhaseContext.Provider value={context}>{children}</CurrentRightPanelPhaseContext.Provider>;
};
+1 -1
View File
@@ -54,7 +54,7 @@ export function diffDeletion(oldStr: string, newStr: string): IDiff {
* @param {String} oldValue the previous value
* @param {String} newValue the new value
* @param {Number} caretPosition the position of the caret after `newValue` was applied.
* @return {object} an object with `at` as the offset where characters were removed and/or added,
* @returns {object} an object with `at` as the offset where characters were removed and/or added,
* `added` with the added string (if any), and
* `removed` with the removed string (if any)
*/
+7 -7
View File
@@ -27,13 +27,13 @@ import { type Caret } from "./caret";
* @param {DocumentPosition?} caretPosition the position where the caret should be position
* @param {string?} inputType the inputType of the DOM input event
* @param {object?} diff an object with `removed` and `added` strings
* @return {Number?} addedLen how many characters were added/removed (-) before the caret during the transformation step.
* @returns {Number?} addedLen how many characters were added/removed (-) before the caret during the transformation step.
* This is used to adjust the caret position.
*/
/**
* @callback ManualTransformCallback
* @return the caret position
* @returns the caret position
*/
type TransformCallback = (caretPosition: DocumentPosition, inputType: string | undefined, diff: IDiff) => number | void;
@@ -179,7 +179,7 @@ export default class EditorModel {
* Should be run inside a `model.transform()` callback.
* @param {Part[]} parts the parts to replace the range with
* @param {DocumentPosition} position the position to start inserting at
* @return {Number} the amount of characters added
* @returns {Number} the amount of characters added
*/
public insert(parts: Part[], position: IPosition): number {
const insertIndex = this.splitAt(position);
@@ -303,7 +303,7 @@ export default class EditorModel {
* removes `len` amount of characters at `pos`.
* @param {Object} pos
* @param {Number} len
* @return {Number} how many characters before pos were also removed,
* @returns {Number} how many characters before pos were also removed,
* usually because of non-editable parts that can only be removed in their entirety.
*/
public removeText(pos: IPosition, len: number): number {
@@ -363,7 +363,7 @@ export default class EditorModel {
* @param {Object} pos
* @param {string} str
* @param {string} inputType the source of the input, see html InputEvent.inputType
* @return {Number} how far from position (in characters) the insertion ended.
* @returns {Number} how far from position (in characters) the insertion ended.
* This can be more than the length of `str` when crossing non-editable parts, which are skipped.
*/
private addText(pos: IPosition, str: string, inputType: string | undefined): number {
@@ -434,7 +434,7 @@ export default class EditorModel {
* Starts a range, which can span across multiple parts, to find and replace text.
* @param {DocumentPosition} positionA a boundary of the range
* @param {DocumentPosition?} positionB the other boundary of the range, optional
* @return {Range}
* @returns {Range}
*/
public startRange(positionA: DocumentPosition, positionB = positionA): Range {
return new Range(this, positionA, positionB);
@@ -463,7 +463,7 @@ export default class EditorModel {
* Performs a transformation not part of an update cycle.
* Modifying the model should only happen inside a transform call if not part of an update call.
* @param {ManualTransformCallback} callback to run the transformations in
* @return {Promise} a promise when auto-complete (if applicable) is done updating
* @returns {Promise} a promise when auto-complete (if applicable) is done updating
*/
public transform(callback: ManualTransformCallback): Promise<void> {
const pos = callback();
+1 -1
View File
@@ -93,7 +93,7 @@ export default class Range {
* Splits the model at the range boundaries and replaces with the given parts.
* Should be run inside a `model.transform()` callback.
* @param {Part[]} parts the parts to replace the range with
* @return {Number} the net amount of characters added, can be negative.
* @returns {Number} the net amount of characters added, can be negative.
*/
public replace(parts: Part[]): number {
const newLength = parts.reduce((sum, part) => sum + part.text.length, 0);
+1 -1
View File
@@ -16,7 +16,7 @@ type Fn<T> = () => Promise<T>;
* @param fn function to memoize
* @param deps React hooks dependencies for the function
* @param initialValue initial value
* @return tuple of cached value and refresh callback
* @returns tuple of cached value and refresh callback
*/
export function useAsyncRefreshMemo<T>(fn: Fn<T>, deps: DependencyList, initialValue: T): [T, () => void];
export function useAsyncRefreshMemo<T>(fn: Fn<T>, deps: DependencyList, initialValue?: T): [T | undefined, () => void];
+1 -1
View File
@@ -177,7 +177,7 @@ export function useEventEmitterAsyncState<T, Events extends string, Arguments ex
}
});
},
[fn, ...deps], // eslint-disable-line react-compiler/react-compiler
[fn, ...deps], // eslint-disable-line react-compiler/react-compiler, react-hooks/exhaustive-deps
);
// re-run when the emitter changes
+16 -16
View File
@@ -76,7 +76,7 @@ export default abstract class BaseEventIndexManager {
* optionally enable event indexing they may override this method to perform
* the necessary runtime checks here.
*
* @return {Promise} A promise that will resolve to true if event indexing
* @returns {Promise} A promise that will resolve to true if event indexing
* is supported, false otherwise.
*/
public async supportsEventIndexing(): Promise<boolean> {
@@ -88,7 +88,7 @@ export default abstract class BaseEventIndexManager {
* @param {string} userId The event that should be added to the index.
* @param {string} deviceId The profile of the event sender at the
*
* @return {Promise} A promise that will resolve when the event index is
* @returns {Promise} A promise that will resolve when the event index is
* initialized.
*/
public async initEventIndex(userId: string, deviceId: string): Promise<void> {
@@ -102,7 +102,7 @@ export default abstract class BaseEventIndexManager {
* @param {IMatrixProfile} profile The profile of the event sender at the
* time the event was received.
*
* @return {Promise} A promise that will resolve when the was queued up for
* @returns {Promise} A promise that will resolve when the was queued up for
* addition.
*/
public async addEventToIndex(ev: IMatrixEvent, profile: IMatrixProfile): Promise<void> {
@@ -123,7 +123,7 @@ export default abstract class BaseEventIndexManager {
* @param {string} roomId The ID of the room which we want to check if it
* has been already indexed.
*
* @return {Promise<boolean>} Returns true if the index contains events for
* @returns {Promise<boolean>} Returns true if the index contains events for
* the given room, false otherwise.
*/
public isRoomIndexed(roomId: string): Promise<boolean> {
@@ -133,7 +133,7 @@ export default abstract class BaseEventIndexManager {
/**
* Get statistical information of the index.
*
* @return {Promise<IIndexStats>} A promise that will resolve to the index
* @returns {Promise<IIndexStats>} A promise that will resolve to the index
* statistics.
*/
public async getStats(): Promise<IIndexStats> {
@@ -142,7 +142,7 @@ export default abstract class BaseEventIndexManager {
/**
* Get the user version of the database.
* @return {Promise<number>} A promise that will resolve to the user stored
* @returns {Promise<number>} A promise that will resolve to the user stored
* version number.
*/
public async getUserVersion(): Promise<number> {
@@ -155,7 +155,7 @@ export default abstract class BaseEventIndexManager {
* @param {number} version The new version that should be stored in the
* database.
*
* @return {Promise<void>} A promise that will resolve once the new version
* @returns {Promise<void>} A promise that will resolve once the new version
* is stored.
*/
public async setUserVersion(version: number): Promise<void> {
@@ -165,7 +165,7 @@ export default abstract class BaseEventIndexManager {
/**
* Commit the previously queued up events to the index.
*
* @return {Promise} A promise that will resolve once the queued up events
* @returns {Promise} A promise that will resolve once the queued up events
* were added to the index.
*/
public async commitLiveEvents(): Promise<void> {
@@ -178,7 +178,7 @@ export default abstract class BaseEventIndexManager {
* @param {ISearchArgs} searchArgs The search configuration for the search,
* sets the search term and determines the search result contents.
*
* @return {Promise<IResultRoomEvents[]>} A promise that will resolve to an array
* @returns {Promise<IResultRoomEvents[]>} A promise that will resolve to an array
* of search results once the search is done.
*/
public async searchEventIndex(searchArgs: ISearchArgs): Promise<IResultRoomEvents> {
@@ -199,7 +199,7 @@ export default abstract class BaseEventIndexManager {
* to fetch the current batch of events. This checkpoint will be removed
* from the index.
*
* @return {Promise} A promise that will resolve to true if all the events
* @returns {Promise} A promise that will resolve to true if all the events
* were already added to the index, false otherwise.
*/
public async addHistoricEvents(
@@ -216,7 +216,7 @@ export default abstract class BaseEventIndexManager {
* @param {ICrawlerCheckpoint} checkpoint The checkpoint that should be added
* to the index.
*
* @return {Promise} A promise that will resolve once the checkpoint has
* @returns {Promise} A promise that will resolve once the checkpoint has
* been stored.
*/
public async addCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise<void> {
@@ -229,7 +229,7 @@ export default abstract class BaseEventIndexManager {
* @param {ICrawlerCheckpoint} checkpoint The checkpoint that should be
* removed from the index.
*
* @return {Promise} A promise that will resolve once the checkpoint has
* @returns {Promise} A promise that will resolve once the checkpoint has
* been removed.
*/
public async removeCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise<void> {
@@ -239,7 +239,7 @@ export default abstract class BaseEventIndexManager {
/**
* Load the stored checkpoints from the index.
*
* @return {Promise<[ICrawlerCheckpoint]>} A promise that will resolve to an
* @returns {Promise<[ICrawlerCheckpoint]>} A promise that will resolve to an
* array of crawler checkpoints once they have been loaded from the index.
*/
public async loadCheckpoints(): Promise<ICrawlerCheckpoint[]> {
@@ -258,7 +258,7 @@ export default abstract class BaseEventIndexManager {
* @param {string} args.direction The direction to which we should continue
* loading events from. This is used only if fromEvent is used as well.
*
* @return {Promise<[IEventAndProfile]>} A promise that will resolve to an
* @returns {Promise<[IEventAndProfile]>} A promise that will resolve to an
* array of Matrix events that contain mxc URLs accompanied with the
* historic profile of the sender.
*/
@@ -269,7 +269,7 @@ export default abstract class BaseEventIndexManager {
/**
* close our event index.
*
* @return {Promise} A promise that will resolve once the event index has
* @returns {Promise} A promise that will resolve once the event index has
* been closed.
*/
public async closeEventIndex(): Promise<void> {
@@ -279,7 +279,7 @@ export default abstract class BaseEventIndexManager {
/**
* Delete our current event index.
*
* @return {Promise} A promise that will resolve once the event index has
* @returns {Promise} A promise that will resolve once the event index has
* been deleted.
*/
public async deleteEventIndex(): Promise<void> {
+3 -3
View File
@@ -711,7 +711,7 @@ export default class EventIndex extends EventEmitter {
* @param {ISearchArgs} searchArgs The search configuration for the search,
* sets the search term and determines the search result contents.
*
* @return {Promise<IResultRoomEvents[]>} A promise that will resolve to an array
* @returns {Promise<IResultRoomEvents[]>} A promise that will resolve to an array
* of search results once the search is done.
*/
public async search(searchArgs: ISearchArgs): Promise<IResultRoomEvents | undefined> {
@@ -949,7 +949,7 @@ export default class EventIndex extends EventEmitter {
/**
* Get statistical information of the index.
*
* @return {Promise<IIndexStats>} A promise that will resolve to the index
* @returns {Promise<IIndexStats>} A promise that will resolve to the index
* statistics.
*/
public async getStats(): Promise<IIndexStats | undefined> {
@@ -963,7 +963,7 @@ export default class EventIndex extends EventEmitter {
* @param {string} roomId The ID of the room which we want to check if it
* has been already indexed.
*
* @return {Promise<boolean>} Returns true if the index contains events for
* @returns {Promise<boolean>} Returns true if the index contains events for
* the given room, false otherwise.
*/
public async isRoomIndexed(roomId: string): Promise<boolean | undefined> {
+6 -6
View File
@@ -37,7 +37,7 @@ export class EventIndexPeg {
* Initialize the EventIndexPeg and if event indexing is enabled initialize
* the event index.
*
* @return {Promise<boolean>} A promise that will resolve to true if an
* @returns {Promise<boolean>} A promise that will resolve to true if an
* EventIndex was successfully initialized, false otherwise.
*/
public async init(): Promise<boolean> {
@@ -111,7 +111,7 @@ export class EventIndexPeg {
/**
* Check if the current platform has support for event indexing.
*
* @return {boolean} True if it has support, false otherwise. Note that this
* @returns {boolean} True if it has support, false otherwise. Note that this
* does not mean that support is installed.
*/
public platformHasSupport(): boolean {
@@ -125,7 +125,7 @@ export class EventIndexPeg {
* this tells us if those are installed. Note that this should only be
* called after the init() method was called.
*
* @return {boolean} True if support is installed, false otherwise.
* @returns {boolean} True if support is installed, false otherwise.
*/
public supportIsInstalled(): boolean {
return this._supportIsInstalled;
@@ -134,7 +134,7 @@ export class EventIndexPeg {
/**
* Get the current event index.
*
* @return {EventIndex} The current event index.
* @returns {EventIndex} The current event index.
*/
public get(): EventIndex | null {
return this.index;
@@ -155,7 +155,7 @@ export class EventIndexPeg {
*
* After a call to this the init() method will need to be called again.
*
* @return {Promise} A promise that will resolve once the event index is
* @returns {Promise} A promise that will resolve once the event index is
* closed.
*/
public async unset(): Promise<void> {
@@ -169,7 +169,7 @@ export class EventIndexPeg {
*
* After a call to this the init() method will need to be called again.
*
* @return {Promise} A promise that will resolve once the event index is
* @returns {Promise} A promise that will resolve once the event index is
* deleted.
*/
public async deleteEventIndex(): Promise<void> {
@@ -37,7 +37,7 @@ export class PushRuleVectorState {
/**
* Convert a PushRuleVectorState to a list of actions
*
* @return [object] list of push-rule actions
* @returns [object] list of push-rule actions
*/
public static actionsFor(pushRuleVectorState?: VectorState): PushRuleAction[] {
if (pushRuleVectorState === VectorState.On) {
+1 -1
View File
@@ -15,7 +15,7 @@ const PHONE_NUMBER_REGEXP = /^[0-9 -.]+$/;
* @param {String} phoneNumber The string to validate. This could be
* either an international format number (MSISDN or e.164) or
* a national-format number.
* @return True if the number could be a valid phone number, otherwise false.
* @returns True if the number could be a valid phone number, otherwise false.
*/
export function looksValid(phoneNumber: string): boolean {
return PHONE_NUMBER_REGEXP.test(phoneNumber);
+9 -9
View File
@@ -109,7 +109,7 @@ export class ConsoleLogger {
/**
* Retrieve log lines to flush to disk.
* @param {boolean} keepLogs True to not delete logs after flushing.
* @return {string} \n delimited log lines to flush.
* @returns {string} \n delimited log lines to flush.
*/
public flush(keepLogs?: boolean): string {
// The ConsoleLogger doesn't care how these end up on disk, it just
@@ -139,7 +139,7 @@ export class IndexedDBLogStore {
}
/**
* @return {Promise} Resolves when the store is ready.
* @returns {Promise} Resolves when the store is ready.
*/
public connect(): Promise<void> {
const req = this.indexedDB.open("logs");
@@ -195,7 +195,7 @@ export class IndexedDBLogStore {
* This guarantees that we will always eventually do a flush when flush() is
* called.
*
* @return {Promise} Resolved when the logs have been flushed.
* @returns {Promise} Resolved when the logs have been flushed.
*/
public flush(): Promise<void> {
// check if a flush() operation is ongoing
@@ -250,7 +250,7 @@ export class IndexedDBLogStore {
* returned are deleted at the same time, so this can be called at startup
* to do house-keeping to keep the logs from growing too large.
*
* @return {Promise<Object[]>} Resolves to an array of objects. The array is
* @returns {Promise<Object[]>} Resolves to an array of objects. The array is
* sorted in time (oldest first) based on when the log file was created (the
* log ID). The objects have said log ID in an "id" field and "lines" which
* is a big string with all the new-line delimited logs.
@@ -455,7 +455,7 @@ export class IndexedDBLogStore {
* @param {Function} resultMapper A function which is repeatedly called with a
* Cursor.
* Return the data you want to keep.
* @return {Promise<T[]>} Resolves to an array of whatever you returned from
* @returns {Promise<T[]>} Resolves to an array of whatever you returned from
* resultMapper.
*/
function selectQuery<T>(
@@ -487,7 +487,7 @@ function selectQuery<T>(
* Modifies globals.
* @param {boolean} setUpPersistence When true (default), the persistence will
* be set up immediately for the logs.
* @return {Promise} Resolves when set up.
* @returns {Promise} Resolves when set up.
*/
export function init(setUpPersistence = true): Promise<void> {
if (global.mx_rage_initPromise) {
@@ -512,7 +512,7 @@ export function init(setUpPersistence = true): Promise<void> {
/**
* Try to start up the rageshake storage for logs. If not possible (client unsupported)
* then this no-ops.
* @return {Promise} Resolves when complete.
* @returns {Promise} Resolves when complete.
*/
export function tryInitStorage(): Promise<void> {
if (global.mx_rage_initStoragePromise) {
@@ -558,7 +558,7 @@ export function flush(): void {
* @deprecated There is no need to call this explicitly: it will be done as a side-effect of {@link tryInitStorage},
* or {@link init} with `setUpPersistence: true`.
*
* @return {Promise} Resolves if cleaned logs.
* @returns {Promise} Resolves if cleaned logs.
*/
export async function cleanup(): Promise<void> {
if (!global.mx_rage_store) {
@@ -570,7 +570,7 @@ export async function cleanup(): Promise<void> {
/**
* Get a recent snapshot of the logs, ready for attaching to a bug report
*
* @return {Array<{lines: string, id, string}>} list of log data
* @returns {Array<{lines: string, id, string}>} list of log data
*/
export async function getLogsForReport(): Promise<{ lines: string; id: string }[]> {
if (!global.mx_rage_logger) {
+3 -3
View File
@@ -340,7 +340,7 @@ async function collectLogs(
*
* @param {function(string)} opts.progressCallback Callback to call with progress updates
*
* @return {Promise<string>} URL returned by the rageshake server
* @returns {Promise<string>} URL returned by the rageshake server
*
* @throws A RageshakeError when the rageshake server responds with an error. This will be `RS_UNKNOWN` if the
* the server does not respond with an expected body format.
@@ -361,7 +361,7 @@ export default async function sendBugReport(bugReportEndpoint?: string, opts: IO
* Loads a bug report into a tarball.
*
* @param opts optional dictionary of options
* @return Resolves with a Tarball object.
* @returns Resolves with a Tarball object.
*/
export async function loadBugReport(opts: IOpts = {}): Promise<Tar> {
const Tar = (await import("tar-js")).default;
@@ -395,7 +395,7 @@ export async function loadBugReport(opts: IOpts = {}): Promise<Tar> {
* but instead causes the browser to download the files locally.
*
* @param opts optional dictionary of options
* @return Resolved when the bug report is downloaded (or started).
* @returns Resolved when the bug report is downloaded (or started).
*/
export async function downloadBugReport(opts: IOpts = {}): Promise<void> {
const tape = await loadBugReport(opts);
+1 -1
View File
@@ -72,7 +72,7 @@ export default class Resizer<C extends IConfig, I extends ResizeItem<C> = Resize
Gives the distributor for a specific resize handle, as if you would have started
to drag that handle. Can be used to manipulate the size of an item programmatically.
@param {number} handleIndex the index of the resize handle in the container
@return {FixedDistributor} a new distributor for the given handle
@returns {FixedDistributor} a new distributor for the given handle
*/
public forHandleAt(handleIndex: number): FixedDistributor<C, I> | undefined {
const handles = this.getResizeHandles();
+6 -6
View File
@@ -19,7 +19,7 @@ export default class Sizer {
/**
@param {Element} item the dom element being resized
@return {number} how far the edge of the item is from the edge of the container
@returns {number} how far the edge of the item is from the edge of the container
*/
public getItemOffset(item: HTMLElement): number {
const offset = (this.vertical ? item.offsetTop : item.offsetLeft) - this.getOffset();
@@ -32,23 +32,23 @@ export default class Sizer {
/**
@param {Element} item the dom element being resized
@return {number} the width/height of an item in the container
@returns {number} the width/height of an item in the container
*/
public getItemSize(item: HTMLElement): number {
return this.vertical ? item.offsetHeight : item.offsetWidth;
}
/** @return {number} the width/height of the container */
/** @returns {number} the width/height of the container */
public getTotalSize(): number {
return this.vertical ? this.container.offsetHeight : this.container.offsetWidth;
}
/** @return {number} container offset to offsetParent */
/** @returns {number} container offset to offsetParent */
private getOffset(): number {
return this.vertical ? this.container.offsetTop : this.container.offsetLeft;
}
/** @return {number} container offset to document */
/** @returns {number} container offset to document */
private getPageOffset(): number {
let element = this.container;
let offset = 0;
@@ -90,7 +90,7 @@ export default class Sizer {
/**
@param {MouseEvent} event the mouse event
@return {number} the distance between the cursor and the edge of the container,
@returns {number} the distance between the cursor and the edge of the container,
along the applicable axis (vertical or horizontal)
*/
public offsetFromEvent(event: MouseEvent): number {
+12 -12
View File
@@ -276,7 +276,7 @@ export default class SettingsStore {
* @param {string} settingName The setting to look up.
* @param {SettingLevel} atLevel
* The level to get the display name for; Defaults to 'default'.
* @return {String} The display name for the setting, or null if not found.
* @returns {String} The display name for the setting, or null if not found.
*/
public static getDisplayName(settingName: SettingKey, atLevel = SettingLevel.DEFAULT): string | null {
if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null;
@@ -299,7 +299,7 @@ export default class SettingsStore {
/**
* Gets the translated description for a given setting
* @param {string} settingName The setting to look up.
* @return {String} The description for the setting, or null if not found.
* @returns {String} The description for the setting, or null if not found.
*/
public static getDescription(settingName: SettingKey): string | ReactNode {
const description = SETTINGS[settingName]?.description;
@@ -311,7 +311,7 @@ export default class SettingsStore {
/**
* Determines if a setting is also a feature.
* @param {string} settingName The setting to look up.
* @return {boolean} True if the setting is a feature.
* @returns {boolean} True if the setting is a feature.
*/
public static isFeature(settingName: SettingKey): boolean {
if (!SETTINGS[settingName]) return false;
@@ -321,7 +321,7 @@ export default class SettingsStore {
/**
* Determines if a setting should have a warning sign in the microcopy
* @param {string} settingName The setting to look up.
* @return {boolean} True if the setting should have a warning sign.
* @returns {boolean} True if the setting should have a warning sign.
*/
public static shouldHaveWarning(settingName: SettingKey): boolean {
if (!SETTINGS[settingName]) return false;
@@ -354,7 +354,7 @@ export default class SettingsStore {
* If a setting is not disabled, or no reason is given by the `SettingController`,
* this will return undefined.
* @param {string} settingName The setting to look up.
* @return {string} The reason the setting is disabled.
* @returns {string} The reason the setting is disabled.
*/
public static disabledMessage(settingName: SettingKey): string | undefined {
const disabled = SETTINGS[settingName].controller?.settingDisabled;
@@ -367,7 +367,7 @@ export default class SettingsStore {
* @param {string} settingName The name of the setting to read the value of.
* @param {String} roomId The room ID to read the setting value in, may be null.
* @param {boolean} excludeDefault True to disable using the default value.
* @return {*} The value, or null if not found
* @returns {*} The value, or null if not found
*/
public static getValue<S extends SettingKey>(
settingName: S,
@@ -404,7 +404,7 @@ export default class SettingsStore {
* @param {boolean} explicit If true, this method will not consider other levels, just the one
* provided. Defaults to false.
* @param {boolean} excludeDefault True to disable using the default value.
* @return {*} The value, or null if not found.
* @returns {*} The value, or null if not found.
*/
public static getValueAt<S extends SettingKey>(
level: SettingLevel,
@@ -460,7 +460,7 @@ export default class SettingsStore {
/**
* Gets the default value of a setting.
* @param settingName The name of the setting to read the value of.
* @return The default value
* @returns The default value
*/
public static getDefaultValue<S extends SettingKey>(settingName: S): Settings[S]["default"] {
// Verify that the setting is actually a setting
@@ -498,7 +498,7 @@ export default class SettingsStore {
* @param level The level
* to change the value at.
* @param value The new value of the setting, may be null.
* @return Resolves when the setting has been changed.
* @returns Resolves when the setting has been changed.
*/
public static async setValue<S extends SettingKey>(
settingName: S,
@@ -556,7 +556,7 @@ export default class SettingsStore {
* @param {string} settingName The name of the setting to check.
* @param {String} roomId The room ID to check in, may be null.
* @param {SettingLevel} level The level to check at.
* @return {boolean} True if the user may set the setting, false otherwise.
* @returns {boolean} True if the user may set the setting, false otherwise.
*/
public static canSetValue(settingName: SettingKey, roomId: string | null, level: SettingLevel): boolean {
const setting = SETTINGS[settingName];
@@ -610,7 +610,7 @@ export default class SettingsStore {
* Determines if the given level is supported on this device.
* @param {SettingLevel} level The level
* to check the feasibility of.
* @return {boolean} True if the level is supported, false otherwise.
* @returns {boolean} True if the level is supported, false otherwise.
*/
public static isLevelSupported(level: SettingLevel): boolean {
if (!LEVEL_HANDLERS[level]) return false;
@@ -638,7 +638,7 @@ export default class SettingsStore {
* Determines the first supported level out of all the levels that can be used for a
* specific setting.
* @param {string} settingName The setting name.
* @return {SettingLevel}
* @returns {SettingLevel}
*/
public static firstSupportedLevel(settingName: SettingKey): SettingLevel | null {
// Verify that the setting is actually a setting

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