mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-17 02:16:16 +02:00
Merge remote-tracking branch 'origin/develop' into hs/refactor-upload-logic
This commit is contained in:
commit
178587ff88
36
.github/workflows/tests.yml
vendored
36
.github/workflows/tests.yml
vendored
@ -93,7 +93,7 @@ jobs:
|
||||
if: env.ENABLE_COVERAGE == 'true'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: coverage-${{ matrix.runner }}
|
||||
name: coverage-jest-${{ matrix.runner }}
|
||||
path: |
|
||||
apps/web/coverage
|
||||
!apps/web/coverage/lcov-report
|
||||
@ -124,9 +124,10 @@ jobs:
|
||||
name: Vitest
|
||||
strategy:
|
||||
matrix:
|
||||
package:
|
||||
- shared-components
|
||||
- module-api
|
||||
path:
|
||||
- apps/desktop
|
||||
- packages/shared-components
|
||||
- packages/module-api
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@ -149,30 +150,39 @@ jobs:
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: |
|
||||
packages/${{ matrix.package }}/node_modules/.cache
|
||||
packages/${{ matrix.package }}/node_modules/.vite/vitest
|
||||
key: ${{ hashFiles('pnpm-lock.yaml') }}
|
||||
${{ matrix.path }}/node_modules/.cache
|
||||
${{ matrix.path }}/node_modules/.vite/vitest
|
||||
key: ${{ matrix.path }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Setup playwright
|
||||
uses: ./.github/actions/setup-playwright
|
||||
if: matrix.package == 'shared-components'
|
||||
if: matrix.path == 'packages/shared-components'
|
||||
with:
|
||||
write-cache: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
- name: Run tests
|
||||
working-directory: "packages/${{ matrix.package }}"
|
||||
working-directory: ${{ matrix.path }}
|
||||
run: pnpm test:unit --coverage=$ENABLE_COVERAGE
|
||||
|
||||
# Dump the disk usage on failure, because this job seems to fail with disk fills sometimes
|
||||
- name: df
|
||||
run: df
|
||||
run: df -h && df -i
|
||||
if: ${{ failure() }}
|
||||
|
||||
- name: Calculate artifact name
|
||||
if: env.ENABLE_COVERAGE == 'true'
|
||||
id: artifact
|
||||
run: |
|
||||
NAME=$(basename "$MATRIX_PATH")
|
||||
echo "name=$NAME" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
MATRIX_PATH: ${{ matrix.path }}
|
||||
|
||||
- name: Upload Artifact
|
||||
if: env.ENABLE_COVERAGE == 'true'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: coverage-${{ matrix.package }}
|
||||
name: coverage-${{ steps.artifact.outputs.name }}
|
||||
path: |
|
||||
packages/${{ matrix.package }}/coverage
|
||||
!packages/${{ matrix.package }}/coverage/lcov-report
|
||||
${{ matrix.path }}/coverage
|
||||
!${{ matrix.path }}/coverage/lcov-report
|
||||
|
||||
@ -62,4 +62,10 @@ CHANGELOG.md
|
||||
/packages/shared-components/typedoc/
|
||||
/packages/shared-components/storybook-static/
|
||||
|
||||
# These files are generated by running `pnpm -r lint:types` and do not adhere to prettier's requirements.
|
||||
# All of them are .gitignored within their parent directory.
|
||||
/packages/playwright-common/lib/
|
||||
/packages/module-api/lib/
|
||||
/packages/module-api/temp/
|
||||
|
||||
/.nx/
|
||||
|
||||
@ -1,3 +1,10 @@
|
||||
Changes in [1.12.17](https://github.com/element-hq/element-web/releases/tag/v1.12.17) (2026-04-30)
|
||||
==================================================================================================
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* [Backport] Fix OIDC login callback handling on Element Desktop ([#33337](https://github.com/element-hq/element-web/pull/33337)). Contributed by @t3chguy.
|
||||
|
||||
|
||||
Changes in [1.12.16](https://github.com/element-hq/element-web/releases/tag/v1.12.16) (2026-04-28)
|
||||
==================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
@ -86,5 +86,12 @@ module.exports = {
|
||||
"@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"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -52,6 +52,7 @@ interface Variant extends Metadata {
|
||||
}
|
||||
|
||||
type Writable<T> = NonNullable<
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
T extends Function ? T : T extends object ? { -readonly [K in keyof T]: Writable<T[K]> } : T
|
||||
>;
|
||||
|
||||
@ -74,7 +75,7 @@ if (process.env.VARIANT_PATH) {
|
||||
}
|
||||
|
||||
for (const key in variant) {
|
||||
console.log(`${key}: ${variant[key]}`);
|
||||
console.log(`${key}: ${variant[key as keyof Variant]}`);
|
||||
}
|
||||
|
||||
interface Configuration extends BaseConfiguration {
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"productName": "Element",
|
||||
"main": "lib/electron-main.js",
|
||||
"exports": "./lib/electron-main.js",
|
||||
"version": "1.12.16",
|
||||
"version": "1.12.17",
|
||||
"description": "Element: the future of secure communication",
|
||||
"author": {
|
||||
"name": "Element",
|
||||
@ -32,8 +32,9 @@
|
||||
"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 && prettier --log-level=warn --write .",
|
||||
"lint:types": "pnpm lint:types:src && pnpm lint:types:test && pnpm lint:types:scripts && pnpm lint:types:hak",
|
||||
"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",
|
||||
"lint:types:test": "tsc --noEmit -p playwright/tsconfig.json",
|
||||
"lint:types:scripts": "tsc --noEmit -p scripts/tsconfig.json",
|
||||
"lint:types:hak": "tsc --noEmit -p hak/tsconfig.json",
|
||||
@ -49,6 +50,7 @@
|
||||
"docker:install": "scripts/in-docker.sh pnpm install",
|
||||
"clean": "rimraf webapp.asar dist packages deploys lib",
|
||||
"hak": "node scripts/hak/index.ts",
|
||||
"test:unit": "vitest",
|
||||
"test:playwright": "nx test:playwright --",
|
||||
"test:playwright:open": "nx test:playwright -- --ui",
|
||||
"test:playwright:screenshots": "nx test:playwright:screenshots --",
|
||||
@ -70,6 +72,7 @@
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@electron/asar": "4.2.0",
|
||||
"@electron/fuses": "^2.1.1",
|
||||
"@element-hq/vite-common": "workspace:*",
|
||||
"@playwright/test": "catalog:",
|
||||
"@stylistic/eslint-plugin": "^5.0.0",
|
||||
"@types/auto-launch": "^5.0.1",
|
||||
@ -79,6 +82,7 @@
|
||||
"@types/pacote": "^11.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"app-builder-lib": "26.9.0",
|
||||
"chokidar": "^5.0.0",
|
||||
"detect-libc": "^2.0.0",
|
||||
@ -95,17 +99,20 @@
|
||||
"eslint-plugin-unicorn": "^56.0.0",
|
||||
"glob": "^13.0.0",
|
||||
"matrix-web-i18n": "catalog:",
|
||||
"memfs": "^4.57.2",
|
||||
"mkdirp": "^3.0.0",
|
||||
"pacote": "^21.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"rimraf": "^6.0.0",
|
||||
"tar": "^7.5.8",
|
||||
"typescript": "6.0.3"
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "catalog:",
|
||||
"vitest-sonar-reporter": "catalog:"
|
||||
},
|
||||
"hakDependencies": {
|
||||
"matrix-seshat": "4.2.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
|
||||
"packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8",
|
||||
"nx": {
|
||||
"includedScripts": []
|
||||
}
|
||||
|
||||
87
apps/desktop/src/protocol.test.ts
Normal file
87
apps/desktop/src/protocol.test.ts
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright 2026 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.
|
||||
*/
|
||||
|
||||
import { expect, describe, it, beforeEach, vi } from "vitest";
|
||||
import { fs as memfs, vol } from "memfs";
|
||||
|
||||
import ProtocolHandler from "./protocol.js";
|
||||
|
||||
const TEST_PROTOCOL = "test.proto";
|
||||
const TEST_SESSION_ID = "test_session_id";
|
||||
const USER_DATA_DIR = "/Users/name/Library/Application Support/Element";
|
||||
|
||||
vi.mock("node:fs", () => ({ default: memfs }));
|
||||
vi.mock("electron", () => ({
|
||||
app: {
|
||||
getPath: vi.fn().mockReturnValue("/Users/name/Library/Application Support/Element"),
|
||||
on: vi.fn(),
|
||||
},
|
||||
ipcMain: {
|
||||
handle: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset the state of the in-memory fs
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
describe("ProtocolHandler", () => {
|
||||
describe("getProfileFromDeeplink", () => {
|
||||
const handler = new ProtocolHandler(TEST_PROTOCOL);
|
||||
|
||||
beforeEach(() => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
"./sso-sessions.json": JSON.stringify({ [TEST_SESSION_ID]: USER_DATA_DIR }),
|
||||
},
|
||||
USER_DATA_DIR,
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle legacy SSO URIs", () => {
|
||||
expect(
|
||||
handler.getProfileFromDeeplink([
|
||||
"Element.app",
|
||||
`element://vector/webapp/?element-desktop-ssoid=${TEST_SESSION_ID}`,
|
||||
]),
|
||||
).toBe(USER_DATA_DIR);
|
||||
});
|
||||
|
||||
it("should handle OIDC URIs with response_mode=query", () => {
|
||||
expect(
|
||||
handler.getProfileFromDeeplink([
|
||||
"Element.app",
|
||||
`${TEST_PROTOCOL}:/vector/webapp/?no_universal_links=true&code=DEADBEEF&state=foobar:element-desktop-ssoid:${TEST_SESSION_ID}`,
|
||||
]),
|
||||
).toBe(USER_DATA_DIR);
|
||||
});
|
||||
|
||||
it("should handle OIDC URIs with response_mode=fragment", () => {
|
||||
expect(
|
||||
handler.getProfileFromDeeplink([
|
||||
"Element.app",
|
||||
`${TEST_PROTOCOL}:/vector/webapp/?no_universal_links=true#code=DEADBEEF&state=foobar:element-desktop-ssoid:${TEST_SESSION_ID}`,
|
||||
]),
|
||||
).toBe(USER_DATA_DIR);
|
||||
});
|
||||
|
||||
it("should handle malformed OIDC URIs gracefully", () => {
|
||||
expect(
|
||||
handler.getProfileFromDeeplink([
|
||||
"Element.app",
|
||||
`${TEST_PROTOCOL}:/vector/webapp/?no_universal_links=true#code=DEADBEEF:element-desktop-ssoid:${TEST_SESSION_ID}`,
|
||||
]),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should handle unrelated URIs gracefully", () => {
|
||||
expect(handler.getProfileFromDeeplink(["Element.app", `${TEST_PROTOCOL}:/vector/webapp/`])).toBeUndefined();
|
||||
expect(handler.getProfileFromDeeplink(["Element.app", `test.unrelated:/vector/webapp/`])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -97,7 +97,8 @@ export default class ProtocolHandler {
|
||||
const s = fs.readFileSync(storePath, { encoding: "utf8" });
|
||||
const o = JSON.parse(s);
|
||||
return typeof o === "object" ? o : {};
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.warn("Unable to read protocol store, starting with empty store: ", e);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@ -130,10 +131,26 @@ export default class ProtocolHandler {
|
||||
let sessionId = parsedUrl.searchParams.get(SEARCH_PARAM);
|
||||
if (!sessionId) {
|
||||
// In OIDC, we must shuttle the value in the `state` param rather than `element-desktop-ssoid`
|
||||
// We encode it as a suffix like `:element-desktop-ssoid:XXYYZZ`
|
||||
sessionId = parsedUrl.searchParams.get("state")!.split(`:${SEARCH_PARAM}:`)[1];
|
||||
// We encode it as a suffix like `:element-desktop-ssoid:XXYYZZ`.
|
||||
// The OIDC flow may have used response_mode=fragment or query, so we need to handle both cases.
|
||||
let searchParams = parsedUrl.searchParams;
|
||||
if (parsedUrl.hash.includes("=")) {
|
||||
const [params] = parsedUrl.hash.substring(1).split("?", 2);
|
||||
searchParams = new URLSearchParams(params);
|
||||
}
|
||||
|
||||
const state = searchParams.get("state");
|
||||
if (state) {
|
||||
sessionId = state.split(`:${SEARCH_PARAM}:`)[1];
|
||||
}
|
||||
}
|
||||
console.log("Forwarding to profile: ", store[sessionId]);
|
||||
|
||||
if (!sessionId) {
|
||||
console.warn("Unable to read session ID in deeplink url:", deeplinkUrl);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
console.log("Forwarding to profile:", store[sessionId]);
|
||||
return store[sessionId];
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,5 +14,6 @@
|
||||
"lib": ["es2022", "es2024.promise"],
|
||||
"strict": true
|
||||
},
|
||||
"include": ["./src/**/*.ts", "./src/**/*.cts"]
|
||||
"include": ["./src/**/*.ts", "./src/**/*.cts"],
|
||||
"exclude": ["./src/**/*.test.ts"]
|
||||
}
|
||||
|
||||
15
apps/desktop/tsconfig.node.json
Normal file
15
apps/desktop/tsconfig.node.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"target": "es2022",
|
||||
"sourceMap": false,
|
||||
"typeRoots": [],
|
||||
"types": [],
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["./electron-builder.ts", "./vitest.config.ts", "./src/**/*.d.ts", "./src/**/*.test.ts"]
|
||||
}
|
||||
23
apps/desktop/vitest.config.ts
Normal file
23
apps/desktop/vitest.config.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { defineConfig, mergeConfig } from "vitest/config";
|
||||
import baseConfig from "@element-hq/vite-common/vite.config.js";
|
||||
|
||||
export default mergeConfig(
|
||||
baseConfig,
|
||||
defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
// The coverage report currently chokes on this file as it doesn't process it as TypeScript
|
||||
exclude: ["src/preload.cts"],
|
||||
},
|
||||
include: ["src/**/*.test.ts"],
|
||||
},
|
||||
}),
|
||||
true,
|
||||
);
|
||||
@ -15,7 +15,7 @@ WORKDIR /src
|
||||
COPY --parents package.json pnpm-lock.yaml pnpm-workspace.yaml patches scripts **/package.json /src/
|
||||
RUN corepack enable
|
||||
RUN --mount=type=bind,source=.git,target=/src/.git /src/scripts/docker-link-repos.sh
|
||||
RUN pnpm install
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Build
|
||||
COPY --link --exclude=.git --exclude=apps/web/docker . /src
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "element-web",
|
||||
"version": "1.12.16",
|
||||
"version": "1.12.17",
|
||||
"description": "Element: the future of secure communication",
|
||||
"author": "New Vector Ltd.",
|
||||
"repository": {
|
||||
@ -244,6 +244,6 @@
|
||||
"engines": {
|
||||
"node": ">=22.18"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
|
||||
"packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8",
|
||||
"private": true
|
||||
}
|
||||
|
||||
@ -258,6 +258,47 @@ test.describe("Room list custom sections", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Collapse and expand all sections", () => {
|
||||
test("should collapse all sections when 'Collapse all sections' button is clicked", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
await createCustomSection(page, "Work");
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
const header = getRoomListHeader(page);
|
||||
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
await expect(getSectionHeader(page, "Work")).toBeVisible();
|
||||
|
||||
const collapseButton = header.getByRole("button", { name: "Collapse all sections" });
|
||||
await expect(collapseButton).toBeVisible();
|
||||
|
||||
await expect(roomList.getByRole("row", { name: "Open room my room" })).toBeVisible();
|
||||
|
||||
await collapseButton.click();
|
||||
|
||||
await expect(getSectionHeader(page, "Chats")).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(getSectionHeader(page, "Work")).toHaveAttribute("aria-expanded", "false");
|
||||
});
|
||||
|
||||
test("should expand all sections when 'Expand all sections' button is clicked", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
await createCustomSection(page, "Work");
|
||||
|
||||
const roomList = getRoomList(page);
|
||||
const header = getRoomListHeader(page);
|
||||
|
||||
await expect(getSectionHeader(page, "Chats")).toBeVisible();
|
||||
|
||||
await header.getByRole("button", { name: "Collapse all sections" }).click();
|
||||
await expect(roomList.getByRole("row", { name: "Open room my room" })).not.toBeVisible();
|
||||
|
||||
await header.getByRole("button", { name: "Expand all sections" }).click();
|
||||
|
||||
await expect(getSectionHeader(page, "Chats")).toHaveAttribute("aria-expanded", "true");
|
||||
await expect(getSectionHeader(page, "Work")).toHaveAttribute("aria-expanded", "true");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Adding a room to a custom section", () => {
|
||||
test("should add a room to a custom section via the More Options menu", async ({ page, app }) => {
|
||||
await app.client.createRoom({ name: "my room" });
|
||||
|
||||
@ -751,7 +751,7 @@ test.describe("Timeline", () => {
|
||||
await expect(page.locator(".mx_EventTile[data-layout=irc] .mx_ViewSourceEvent_expanded")).toBeVisible();
|
||||
});
|
||||
|
||||
test("should render file size in kibibytes on a file tile", async ({ page, room }) => {
|
||||
test("should render file size in kibibytes on a file tile", async ({ page, app, room }) => {
|
||||
await page.goto(`/#/room/${room.roomId}`);
|
||||
await expect(
|
||||
page
|
||||
@ -760,6 +760,7 @@ test.describe("Timeline", () => {
|
||||
).toBeVisible();
|
||||
|
||||
// Upload a file from the message composer
|
||||
app.
|
||||
await page
|
||||
.locator(".mx_MessageComposer_actions input[type='file']")
|
||||
.setInputFiles(getSampleFilePath("matrix-org-client-versions.json"));
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 59 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
@ -11,7 +11,7 @@ import {
|
||||
} from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js";
|
||||
|
||||
const DOCKER_IMAGE =
|
||||
"ghcr.io/element-hq/matrix-authentication-service:main@sha256:034500c4797287bfcdc4d13304e89ac65ce44a0fa33664836aea6e42c33535fb";
|
||||
"ghcr.io/element-hq/matrix-authentication-service:main@sha256:c765fb602f78e77eccaa8e020e56c39eef99eccbabc9cb0df2c5705f60ca899e";
|
||||
|
||||
/**
|
||||
* MatrixAuthenticationServiceContainer which freezes the docker digest to
|
||||
|
||||
@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js";
|
||||
|
||||
const DOCKER_IMAGE =
|
||||
"ghcr.io/element-hq/synapse:develop@sha256:b2fec2c9460f5b297a3a4ce78037902590240a1978301ed1d4bc97918c451041";
|
||||
"ghcr.io/element-hq/synapse:develop@sha256:53b1c81dc161be1d999344ca727a520972b912fc24681dfafe25fca4b766b2a5";
|
||||
|
||||
/**
|
||||
* SynapseContainer which freezes the docker digest to stabilise tests,
|
||||
|
||||
@ -163,9 +163,10 @@ b {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:link,
|
||||
a:visited {
|
||||
/* Keep the legacy link colour without overriding Compound anchors. */
|
||||
a:where(:not([data-kind])):hover,
|
||||
a:where(:not([data-kind])):link,
|
||||
a:where(:not([data-kind])):visited {
|
||||
color: $accent-alt;
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
/* Shared cascade order: Compound tokens, Compound Web, shared components, then app overrides. */
|
||||
@layer compound-tokens, compound-web, shared-components, app-web;
|
||||
|
||||
/* Modules bundled with compound apply compound lastly. In order to catch issue due to css class ordering, we put compound at the end */
|
||||
@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css") layer(compound);
|
||||
@import url("@vector-im/compound-web/dist/style.css");
|
||||
@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css") layer(compound-tokens);
|
||||
@import url("@vector-im/compound-web/dist/style.css") layer(compound-web);
|
||||
|
||||
@ -11,9 +11,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
/* Container for live recording and playback controls */
|
||||
.mx_MediaBody.mx_VoiceMessagePrimaryContainer {
|
||||
/* The waveform (right) has a 1px padding on it that we want to account for, otherwise */
|
||||
/* inherit from mx_MediaBody */
|
||||
padding-right: 11px;
|
||||
/* Match mx_MediaBody spacing, offsetting the waveform's 1px internal right padding. */
|
||||
padding: 6px 11px 6px 12px;
|
||||
|
||||
/* Cheat at alignment a bit */
|
||||
display: flex;
|
||||
|
||||
@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_CompleteSecurityBody {
|
||||
color: $authpage-primary-color;
|
||||
background-color: $background;
|
||||
border-radius: 4px;
|
||||
border-radius: 24px;
|
||||
padding: 20px 20px 60px 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_MFileBody [data-type="download"] {
|
||||
color: $accent;
|
||||
height: var(--cpd-space-9x);
|
||||
|
||||
& object {
|
||||
|
||||
@ -17,6 +17,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
color: $secondary-content;
|
||||
font: var(--cpd-font-body-md-regular);
|
||||
line-height: $font-24px;
|
||||
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.mx_MAudioBody > .mx_MediaBody {
|
||||
border-radius: var(--MBody-border-radius);
|
||||
}
|
||||
|
||||
@ -19,3 +19,12 @@ Please see LICENSE files in the repository root for full details.
|
||||
opacity: unset; /* Unset the opacity value specified above on the search results panel */
|
||||
}
|
||||
}
|
||||
|
||||
.mx_TextualBody_urlPreviews {
|
||||
/* Let shared-components own preview link colours instead of the app-wide anchor colour. */
|
||||
a:where(:not([data-kind])):hover,
|
||||
a:where(:not([data-kind])):link,
|
||||
a:where(:not([data-kind])):visited {
|
||||
color: revert-layer;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,12 +26,22 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
&.mx_ThemeChoicePanel_themeSelector_disabled {
|
||||
border-color: var(--cpd-color-border-disabled);
|
||||
|
||||
.mx_ThemeChoicePanel_themeSelector_Label {
|
||||
color: var(--cpd-color-text-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ThemeChoicePanel_themeSelector_Label {
|
||||
color: var(--cpd-color-text-primary);
|
||||
font: var(--cpd-font-body-md-semibold);
|
||||
}
|
||||
|
||||
&:not(.mx_ThemeChoicePanel_themeSelector_disabled) {
|
||||
.mx_ThemeChoicePanel_themeSelector_Label {
|
||||
color: var(--cpd-color-text-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
color: $primary-content;
|
||||
|
||||
a {
|
||||
/* Compound links carry data-kind and provide their own colour. */
|
||||
a:not([data-kind]) {
|
||||
color: $links;
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
color: $primary-content;
|
||||
|
||||
a {
|
||||
/* Compound links carry data-kind and provide their own colour. */
|
||||
a:not([data-kind]) {
|
||||
color: $links;
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
@import "../../../../res/css/_font-sizes.pcss";
|
||||
@import "../../legacy-light/css/_fonts.pcss";
|
||||
@import "../../legacy-light/css/_legacy-light.pcss";
|
||||
@import "../../legacy-dark/css/_legacy-dark.pcss";
|
||||
@import "../../light-custom/css/_custom.pcss";
|
||||
@import "../../../../res/css/_components.pcss";
|
||||
@layer compound-tokens, compound-web, shared-components, app-web;
|
||||
|
||||
@import "../../../../res/css/_font-sizes.pcss" layer(app-web);
|
||||
@import "../../legacy-light/css/_fonts.pcss" layer(app-web);
|
||||
@import "../../legacy-light/css/_legacy-light.pcss" layer(app-web);
|
||||
@import "../../legacy-dark/css/_legacy-dark.pcss" layer(app-web);
|
||||
@import "../../light-custom/css/_custom.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_components.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_compound.pcss";
|
||||
@import url("highlight.js/styles/atom-one-light.min.css");
|
||||
@import url("github-markdown-css/github-markdown-dark.css");
|
||||
@import url("highlight.js/styles/atom-one-light.min.css") layer(app-web);
|
||||
@import url("github-markdown-css/github-markdown-dark.css") layer(app-web);
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
@import "../../../../res/css/_font-sizes.pcss";
|
||||
@import "../../light/css/_fonts.pcss";
|
||||
@import "../../light/css/_light.pcss";
|
||||
@import "_dark.pcss";
|
||||
@import "../../light/css/_mods.pcss";
|
||||
@import "../../../../res/css/_components.pcss";
|
||||
@layer compound-tokens, compound-web, shared-components, app-web;
|
||||
|
||||
@import "../../../../res/css/_font-sizes.pcss" layer(app-web);
|
||||
@import "../../light/css/_fonts.pcss" layer(app-web);
|
||||
@import "../../light/css/_light.pcss" layer(app-web);
|
||||
@import "_dark.pcss" layer(app-web);
|
||||
@import "../../light/css/_mods.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_components.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_compound.pcss";
|
||||
@import url("highlight.js/styles/atom-one-dark.min.css");
|
||||
@import url("github-markdown-css/github-markdown-dark.css");
|
||||
@import url("highlight.js/styles/atom-one-dark.min.css") layer(app-web);
|
||||
@import url("github-markdown-css/github-markdown-dark.css") layer(app-web);
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
@import "../../../../res/css/_font-sizes.pcss";
|
||||
@import "../../legacy-light/css/_fonts.pcss";
|
||||
@import "../../legacy-light/css/_legacy-light.pcss";
|
||||
@import "_legacy-dark.pcss";
|
||||
@import "../../../../res/css/_components.pcss";
|
||||
@layer compound-tokens, compound-web, shared-components, app-web;
|
||||
|
||||
@import "../../../../res/css/_font-sizes.pcss" layer(app-web);
|
||||
@import "../../legacy-light/css/_fonts.pcss" layer(app-web);
|
||||
@import "../../legacy-light/css/_legacy-light.pcss" layer(app-web);
|
||||
@import "_legacy-dark.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_components.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_compound.pcss";
|
||||
@import url("highlight.js/styles/atom-one-dark.min.css");
|
||||
@import url("github-markdown-css/github-markdown-dark.css");
|
||||
@import url("highlight.js/styles/atom-one-dark.min.css") layer(app-web);
|
||||
@import url("github-markdown-css/github-markdown-dark.css") layer(app-web);
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
@import "../../../../res/css/_font-sizes.pcss";
|
||||
@import "_fonts.pcss";
|
||||
@import "_legacy-light.pcss";
|
||||
@import "../../../../res/css/_components.pcss";
|
||||
@layer compound-tokens, compound-web, shared-components, app-web;
|
||||
|
||||
@import "../../../../res/css/_font-sizes.pcss" layer(app-web);
|
||||
@import "_fonts.pcss" layer(app-web);
|
||||
@import "_legacy-light.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_components.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_compound.pcss";
|
||||
@import url("highlight.js/styles/atom-one-light.min.css");
|
||||
@import url("github-markdown-css/github-markdown-light.css");
|
||||
@import url("highlight.js/styles/atom-one-light.min.css") layer(app-web);
|
||||
@import url("github-markdown-css/github-markdown-light.css") layer(app-web);
|
||||
|
||||
@ -25,16 +25,17 @@ $panels: var(--panels, var(--cpd-color-gray-600));
|
||||
$panel-actions: var(--panels-actions, var(--cpd-color-gray-300));
|
||||
|
||||
/* --timeline-background-color */
|
||||
$button-secondary-bg-color: var(--timeline-background-color);
|
||||
$lightbox-border-color: var(--timeline-background-color);
|
||||
$menu-bg-color: var(--timeline-background-color);
|
||||
$message-action-bar-bg-color: var(--timeline-background-color);
|
||||
$background: var(--timeline-background-color);
|
||||
$custom-theme-background: var(--timeline-background-color, var(--cpd-color-bg-canvas-default));
|
||||
$button-secondary-bg-color: $custom-theme-background;
|
||||
$lightbox-border-color: $custom-theme-background;
|
||||
$menu-bg-color: $custom-theme-background;
|
||||
$message-action-bar-bg-color: $custom-theme-background;
|
||||
$background: $custom-theme-background;
|
||||
$togglesw-ball-color: var(--cpd-color-bg-action-primary-rest);
|
||||
$togglesw-off-color: var(--togglesw-off-color);
|
||||
$droptarget-bg-color: var(--timeline-background-color-50pct); /* still needs alpha at .5 */
|
||||
$authpage-modal-bg-color: var(--timeline-background-color-50pct); /* still needs alpha at .59 */
|
||||
$roomheader-bg-color: var(--timeline-background-color);
|
||||
$roomheader-bg-color: $custom-theme-background;
|
||||
|
||||
/* --roomlist-highlights-color */
|
||||
$panel-actions: var(--roomlist-highlights-color);
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
@import "../../../../res/css/_font-sizes.pcss";
|
||||
@import "../../legacy-light/css/_fonts.pcss";
|
||||
@import "../../legacy-light/css/_legacy-light.pcss";
|
||||
@import "_custom.pcss";
|
||||
@import "../../../../res/css/_components.pcss";
|
||||
@layer compound-tokens, compound-web, shared-components, app-web;
|
||||
|
||||
@import "../../../../res/css/_font-sizes.pcss" layer(app-web);
|
||||
@import "../../legacy-light/css/_fonts.pcss" layer(app-web);
|
||||
@import "../../legacy-light/css/_legacy-light.pcss" layer(app-web);
|
||||
@import "_custom.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_components.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_compound.pcss";
|
||||
@import url("highlight.js/styles/atom-one-light.min.css");
|
||||
@import url("github-markdown-css/github-markdown-light.css");
|
||||
@import url("highlight.js/styles/atom-one-light.min.css") layer(app-web);
|
||||
@import url("github-markdown-css/github-markdown-light.css") layer(app-web);
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
@import "../../../../res/css/_font-sizes.pcss";
|
||||
@import "../../light/css/_fonts.pcss";
|
||||
@import "../../light/css/_light.pcss";
|
||||
@import "_light-high-contrast.pcss";
|
||||
@import "../../light/css/_mods.pcss";
|
||||
@import "../../../../res/css/_components.pcss";
|
||||
@layer compound-tokens, compound-web, shared-components, app-web;
|
||||
|
||||
@import "../../../../res/css/_font-sizes.pcss" layer(app-web);
|
||||
@import "../../light/css/_fonts.pcss" layer(app-web);
|
||||
@import "../../light/css/_light.pcss" layer(app-web);
|
||||
@import "_light-high-contrast.pcss" layer(app-web);
|
||||
@import "../../light/css/_mods.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_components.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_compound.pcss";
|
||||
@import url("highlight.js/styles/atom-one-light.min.css");
|
||||
@import url("github-markdown-css/github-markdown-light.css");
|
||||
@import url("highlight.js/styles/atom-one-light.min.css") layer(app-web);
|
||||
@import url("github-markdown-css/github-markdown-light.css") layer(app-web);
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
@import "../../../../res/css/_font-sizes.pcss";
|
||||
@import "_fonts.pcss";
|
||||
@import "_light.pcss";
|
||||
@import "_mods.pcss";
|
||||
@import "../../../../res/css/_components.pcss";
|
||||
@layer compound-tokens, compound-web, shared-components, app-web;
|
||||
|
||||
@import "../../../../res/css/_font-sizes.pcss" layer(app-web);
|
||||
@import "_fonts.pcss" layer(app-web);
|
||||
@import "_light.pcss" layer(app-web);
|
||||
@import "_mods.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_components.pcss" layer(app-web);
|
||||
@import "../../../../res/css/_compound.pcss";
|
||||
@import url("highlight.js/styles/atom-one-light.min.css");
|
||||
@import url("github-markdown-css/github-markdown-light.css");
|
||||
@import url("highlight.js/styles/atom-one-light.min.css") layer(app-web);
|
||||
@import url("github-markdown-css/github-markdown-light.css") layer(app-web);
|
||||
|
||||
@ -25,9 +25,9 @@ export interface MediaPreviewConfig extends Record<string, unknown> {
|
||||
/**
|
||||
* Media preview setting for thumbnails of media in rooms.
|
||||
*/
|
||||
media_previews: MediaPreviewValue;
|
||||
media_previews?: MediaPreviewValue;
|
||||
/**
|
||||
* Media preview settings for avatars of rooms we have been invited to.
|
||||
*/
|
||||
invite_avatars: MediaPreviewValue.On | MediaPreviewValue.Off;
|
||||
invite_avatars?: MediaPreviewValue.On | MediaPreviewValue.Off;
|
||||
}
|
||||
|
||||
@ -725,7 +725,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
|
||||
);
|
||||
|
||||
finished.then(([allow]) => {
|
||||
SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow);
|
||||
SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow ?? null);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -297,6 +297,9 @@ class MatrixClientPegClass implements IMatrixClientPeg {
|
||||
opts.lazyLoadMembers = true;
|
||||
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
|
||||
opts.threadSupport = true;
|
||||
if (SettingsStore.getValue("feature_user_status")) {
|
||||
opts.unstableMSC4429SyncUserProfileFields = ["org.matrix.msc4426.status"];
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("feature_sliding_sync")) {
|
||||
throw new UserFriendlyError("sliding_sync_legacy_no_longer_supported");
|
||||
|
||||
@ -142,6 +142,16 @@ interface EmittedEvents {
|
||||
[NotifierEvent.NotificationHiddenChange]: (hidden: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type representing a notification sound setting
|
||||
*/
|
||||
export type NotificationSound = {
|
||||
url: string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
size?: number;
|
||||
};
|
||||
|
||||
class NotifierClass extends TypedEventEmitter<keyof EmittedEvents, EmittedEvents> {
|
||||
private notifsByRoom: Record<string, Notification[]> = {};
|
||||
|
||||
@ -223,12 +233,7 @@ class NotifierClass extends TypedEventEmitter<keyof EmittedEvents, EmittedEvents
|
||||
}
|
||||
}
|
||||
|
||||
public getSoundForRoom(roomId: string): {
|
||||
url: string;
|
||||
name: string;
|
||||
type: string;
|
||||
size: number;
|
||||
} | null {
|
||||
public getSoundForRoom(roomId: string): NotificationSound | null {
|
||||
// We do no caching here because the SDK caches setting
|
||||
// and the browser will cache the sound.
|
||||
const content = SettingsStore.getValue("notificationSound", roomId);
|
||||
|
||||
@ -19,7 +19,7 @@ import SettingsStore from "./settings/SettingsStore";
|
||||
import { type ScreenName } from "./PosthogTrackers";
|
||||
import { type ActionPayload } from "./dispatcher/payloads";
|
||||
import { Action } from "./dispatcher/actions";
|
||||
import { type SettingUpdatedPayload } from "./dispatcher/payloads/SettingUpdatedPayload";
|
||||
import { isSettingUpdatedPayload, type SettingUpdatedPayload } from "./dispatcher/payloads/SettingUpdatedPayload";
|
||||
import dis from "./dispatcher/dispatcher";
|
||||
import { Layout } from "./settings/enums/Layout";
|
||||
|
||||
@ -199,8 +199,8 @@ export class PosthogAnalytics {
|
||||
const settingsPayload = payload as SettingUpdatedPayload;
|
||||
if (["layout", "useCompactLayout"].includes(settingsPayload.settingName)) {
|
||||
this.onLayoutUpdated();
|
||||
} else if (settingsPayload.settingName === "urlPreviewsEnabled" && !settingsPayload.roomId) {
|
||||
this.onUrlPreviewSettingUpdated(settingsPayload.newValue as boolean);
|
||||
} else if (isSettingUpdatedPayload(settingsPayload, "urlPreviewsEnabled") && !settingsPayload.roomId) {
|
||||
this.onUrlPreviewSettingUpdated(settingsPayload.newValue);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -122,7 +122,7 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||
|
||||
private onCrawlerSleepTimeChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
this.setState({ crawlerSleepTime: parseInt(e.target.value, 10) });
|
||||
SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value);
|
||||
SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.valueAsNumber);
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
|
||||
@ -1794,11 +1794,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
SettingsStore.watchSetting(
|
||||
"blacklistUnverifiedDevices",
|
||||
null,
|
||||
(_settingName, _roomId, atLevel, blacklistEnabled: boolean) => {
|
||||
(_settingName, _roomId, atLevel, blacklistEnabled) => {
|
||||
if (atLevel != SettingLevel.DEVICE) {
|
||||
return;
|
||||
}
|
||||
crypto.globalBlacklistUnverifiedDevices = blacklistEnabled;
|
||||
crypto.globalBlacklistUnverifiedDevices = !!blacklistEnabled;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,9 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React from "react";
|
||||
import {
|
||||
ClientRendezvousFailureReason,
|
||||
linkNewDeviceByGeneratingQR,
|
||||
MSC4108FailureReason,
|
||||
MSC4108RendezvousSession,
|
||||
MSC4108SecureChannel,
|
||||
MSC4108SignInWithQR,
|
||||
RendezvousError,
|
||||
type RendezvousFailureReason,
|
||||
@ -55,6 +54,7 @@ export type FailureReason = RendezvousFailureReason | LoginWithQRFailureReason;
|
||||
*/
|
||||
export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
private finished = false;
|
||||
private abortController?: AbortController;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
@ -69,35 +69,31 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.updateMode(this.props.mode).then(() => {});
|
||||
void this.updateMode(this.props.mode);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Readonly<IProps>): void {
|
||||
if (prevProps.mode !== this.props.mode) {
|
||||
this.updateMode(this.props.mode).then(() => {});
|
||||
void this.updateMode(this.props.mode);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateMode(mode: Mode, showLoading = true): Promise<void> {
|
||||
if (this.state.rendezvous) {
|
||||
const rendezvous = this.state.rendezvous;
|
||||
rendezvous.onFailure = undefined;
|
||||
this.setState({ rendezvous: undefined });
|
||||
}
|
||||
this.abortController?.abort();
|
||||
this.abortController = new AbortController();
|
||||
this.setState({ rendezvous: undefined });
|
||||
if (showLoading) {
|
||||
this.setState({ phase: Phase.Loading });
|
||||
}
|
||||
|
||||
if (mode === Mode.Show) {
|
||||
await this.generateAndShowCode();
|
||||
await this.generateAndShowCode(this.abortController);
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
if (this.state.rendezvous && !this.finished) {
|
||||
// eslint-disable-next-line react/no-direct-mutation-state
|
||||
this.state.rendezvous.onFailure = undefined;
|
||||
// calling cancel will call close() as well to clean up the resources
|
||||
this.state.rendezvous.cancel(MSC4108FailureReason.UserCancelled);
|
||||
if (!this.finished) {
|
||||
this.abortController?.abort();
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,24 +102,18 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
this.props.onFinished(success);
|
||||
}
|
||||
|
||||
private generateAndShowCode = async (): Promise<void> => {
|
||||
private generateAndShowCode = async (abortController: AbortController): Promise<void> => {
|
||||
let rendezvous: MSC4108SignInWithQR;
|
||||
try {
|
||||
const transport = new MSC4108RendezvousSession({
|
||||
onFailure: this.onFailure,
|
||||
client: this.props.client,
|
||||
});
|
||||
await transport.send("");
|
||||
const channel = new MSC4108SecureChannel(transport, undefined, this.onFailure);
|
||||
rendezvous = new MSC4108SignInWithQR(channel, false, this.props.client, this.onFailure);
|
||||
|
||||
await rendezvous.generateCode();
|
||||
rendezvous = await linkNewDeviceByGeneratingQR(this.props.client, this.onFailure, abortController.signal);
|
||||
if (abortController.signal.aborted) return;
|
||||
this.setState({
|
||||
phase: Phase.ShowingQR,
|
||||
rendezvous,
|
||||
failureReason: undefined,
|
||||
});
|
||||
} catch (e) {
|
||||
if (abortController.signal.aborted) return;
|
||||
logger.error("Error whilst generating QR code", e);
|
||||
this.setState({ phase: Phase.Error, failureReason: ClientRendezvousFailureReason.HomeserverLacksSupport });
|
||||
return;
|
||||
@ -142,8 +132,9 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
|
||||
// we ask the user to confirm that the channel is secure
|
||||
} catch (e: RendezvousError | unknown) {
|
||||
if (abortController.signal.aborted) return;
|
||||
logger.error("Error whilst approving login", e);
|
||||
await rendezvous?.cancel(
|
||||
await rendezvous.cancel(
|
||||
e instanceof RendezvousError ? (e.code as MSC4108FailureReason) : ClientRendezvousFailureReason.Unknown,
|
||||
);
|
||||
}
|
||||
@ -210,6 +201,7 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
};
|
||||
|
||||
public reset(): void {
|
||||
this.abortController?.abort();
|
||||
this.setState({
|
||||
rendezvous: undefined,
|
||||
verificationUri: undefined,
|
||||
|
||||
@ -13,6 +13,7 @@ import { useCreateAutoDisposedViewModel, DisambiguatedProfileView } from "@eleme
|
||||
|
||||
import { DisambiguatedProfileViewModel } from "../../../viewmodels/room/timeline/event-tile/DisambiguatedProfileViewModel";
|
||||
import { useRoomMemberProfile } from "../../../hooks/room/useRoomMemberProfile";
|
||||
import { useUserStatus } from "../../../hooks/useUserStatus";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
@ -27,6 +28,7 @@ export default function SenderProfile({ mxEvent, onClick, withTooltip }: IProps)
|
||||
userId: sender,
|
||||
member: mxEvent.sender,
|
||||
});
|
||||
const userStatus = useUserStatus(sender);
|
||||
|
||||
const disambiguatedProfileVM = useCreateAutoDisposedViewModel(
|
||||
() =>
|
||||
@ -37,9 +39,13 @@ export default function SenderProfile({ mxEvent, onClick, withTooltip }: IProps)
|
||||
colored: true,
|
||||
emphasizeDisplayName: true,
|
||||
withTooltip,
|
||||
userStatus,
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
disambiguatedProfileVM.setUserStatus(userStatus);
|
||||
}, [disambiguatedProfileVM, userStatus]);
|
||||
useEffect(() => {
|
||||
disambiguatedProfileVM.setMember(sender ?? "", member);
|
||||
}, [disambiguatedProfileVM, member, sender]);
|
||||
|
||||
@ -210,7 +210,7 @@ export function TextualBodyFactory(props: Readonly<IBodyProps>): JSX.Element {
|
||||
vm={textualBodyVm}
|
||||
body={<EventContentBodyView vm={eventContentBodyVm} as={willHaveWrapper ? "span" : "div"} />}
|
||||
bodyRef={contentRef}
|
||||
urlPreviews={<UrlPreviewGroupView vm={urlPreviewVm} />}
|
||||
urlPreviews={<UrlPreviewGroupView vm={urlPreviewVm} className="mx_TextualBody_urlPreviews" />}
|
||||
className={getTextualBodyClassName(content.msgtype as MsgType | undefined)}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -38,8 +38,8 @@ function LayoutSelector(): JSX.Element {
|
||||
<Root
|
||||
className="mx_LayoutSwitcher_LayoutSelector"
|
||||
onChange={async (evt) => {
|
||||
// We don't have any file in the form, we can cast it as string safely
|
||||
const newLayout = new FormData(evt.currentTarget).get("layout") as string | null;
|
||||
// We don't have any file in the form, we can cast it as Layout safely
|
||||
const newLayout = new FormData(evt.currentTarget).get("layout") as Layout;
|
||||
await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, newLayout);
|
||||
}}
|
||||
>
|
||||
|
||||
@ -7,48 +7,35 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
type IServerVersions,
|
||||
type OidcClientConfig,
|
||||
type MatrixClient,
|
||||
DEVICE_CODE_SCOPE,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import QrCodeIcon from "@vector-im/compound-design-tokens/assets/web/icons/qr-code";
|
||||
import { Text } from "@vector-im/compound-web";
|
||||
import { isSignInWithQRAvailable } from "matrix-js-sdk/src/rendezvous";
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import AccessibleButton from "../../elements/AccessibleButton";
|
||||
import { SettingsSubsection } from "../shared/SettingsSubsection";
|
||||
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
|
||||
import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
|
||||
|
||||
interface IProps {
|
||||
onShowQr: () => void;
|
||||
versions?: IServerVersions;
|
||||
oidcClientConfig?: OidcClientConfig;
|
||||
isCrossSigningReady?: boolean;
|
||||
}
|
||||
|
||||
export function shouldShowQr(
|
||||
cli: MatrixClient,
|
||||
isCrossSigningReady: boolean,
|
||||
oidcClientConfig?: OidcClientConfig,
|
||||
versions?: IServerVersions,
|
||||
): boolean {
|
||||
const msc4108Supported = !!versions?.unstable_features?.["org.matrix.msc4108"];
|
||||
export async function shouldShowQrForLinkNewDevice(cli: MatrixClient, isCrossSigningReady: boolean): Promise<boolean> {
|
||||
const doesServerHaveSupport = await isSignInWithQRAvailable(cli);
|
||||
|
||||
const deviceAuthorizationGrantSupported = oidcClientConfig?.grant_types_supported.includes(DEVICE_CODE_SCOPE);
|
||||
|
||||
return (
|
||||
!!deviceAuthorizationGrantSupported &&
|
||||
msc4108Supported &&
|
||||
!!cli.getCrypto()?.exportSecretsBundle &&
|
||||
isCrossSigningReady
|
||||
);
|
||||
return doesServerHaveSupport && !!cli.getCrypto()?.exportSecretsBundle && isCrossSigningReady;
|
||||
}
|
||||
|
||||
const LoginWithQRSection: React.FC<IProps> = ({ onShowQr, versions, oidcClientConfig, isCrossSigningReady }) => {
|
||||
const LoginWithQRSection: React.FC<IProps> = ({ onShowQr, isCrossSigningReady }) => {
|
||||
const cli = useMatrixClientContext();
|
||||
const offerShowQr = shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions);
|
||||
const offerShowQr = useAsyncMemo(
|
||||
() => shouldShowQrForLinkNewDevice(cli, !!isCrossSigningReady),
|
||||
[cli, isCrossSigningReady],
|
||||
false,
|
||||
);
|
||||
|
||||
return (
|
||||
<SettingsSubsection heading={_t("settings|sessions|sign_in_with_qr")}>
|
||||
|
||||
@ -68,8 +68,6 @@ export default class AppearanceUserSettingsTab extends React.Component<EmptyObje
|
||||
evt.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<SettingsFlag name="useCompactLayout" level={SettingLevel.DEVICE} />
|
||||
|
||||
<SettingsFlag
|
||||
name="useBundledEmojiFont"
|
||||
level={SettingLevel.DEVICE}
|
||||
|
||||
@ -211,17 +211,17 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
||||
|
||||
private onAutocompleteDelayChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
this.setState({ autocompleteDelay: e.target.value });
|
||||
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value);
|
||||
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.valueAsNumber);
|
||||
};
|
||||
|
||||
private onReadMarkerInViewThresholdMs = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
this.setState({ readMarkerInViewThresholdMs: e.target.value });
|
||||
SettingsStore.setValue("readMarkerInViewThresholdMs", null, SettingLevel.DEVICE, e.target.value);
|
||||
SettingsStore.setValue("readMarkerInViewThresholdMs", null, SettingLevel.DEVICE, e.target.valueAsNumber);
|
||||
};
|
||||
|
||||
private onReadMarkerOutOfViewThresholdMs = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
this.setState({ readMarkerOutOfViewThresholdMs: e.target.value });
|
||||
SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value);
|
||||
SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.valueAsNumber);
|
||||
};
|
||||
|
||||
private renderGroup(settingIds: BooleanSettingKey[], level = SettingLevel.ACCOUNT): JSX.Element {
|
||||
|
||||
@ -162,14 +162,6 @@ const SessionManagerTab: React.FC<{
|
||||
const disableMultipleSignout = !!accountManagement?.endpoint;
|
||||
const userId = matrixClient?.getUserId();
|
||||
const currentUserMember = (userId && matrixClient?.getUser(userId)) || undefined;
|
||||
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
|
||||
const oidcClientConfig = useAsyncMemo(async () => {
|
||||
try {
|
||||
return await matrixClient?.getAuthMetadata();
|
||||
} catch (e) {
|
||||
logger.error("Failed to discover OIDC metadata", e);
|
||||
}
|
||||
}, [matrixClient]);
|
||||
const isCrossSigningReady = useAsyncMemo(
|
||||
async () => matrixClient.getCrypto()?.isCrossSigningReady() ?? false,
|
||||
[matrixClient],
|
||||
@ -279,12 +271,7 @@ const SessionManagerTab: React.FC<{
|
||||
return (
|
||||
<SettingsTab>
|
||||
<SettingsSection>
|
||||
<LoginWithQRSection
|
||||
onShowQr={onShowQrClicked}
|
||||
versions={clientVersions}
|
||||
oidcClientConfig={oidcClientConfig}
|
||||
isCrossSigningReady={isCrossSigningReady}
|
||||
/>
|
||||
<LoginWithQRSection onShowQr={onShowQrClicked} isCrossSigningReady={isCrossSigningReady} />
|
||||
<SecurityRecommendations
|
||||
devices={devices}
|
||||
goToFilteredList={onGoToFilteredList}
|
||||
|
||||
@ -340,7 +340,7 @@ export class DeviceListener {
|
||||
});
|
||||
}
|
||||
|
||||
private onRecordClientInformationSettingChange: CallbackFn = (
|
||||
private onRecordClientInformationSettingChange: CallbackFn<"deviceClientInformationOptIn"> = (
|
||||
_originalSettingName,
|
||||
_roomId,
|
||||
_level,
|
||||
|
||||
@ -403,4 +403,20 @@ export enum Action {
|
||||
* or keyboard event).
|
||||
*/
|
||||
UserActivity = "user_activity",
|
||||
|
||||
/**
|
||||
* Fired to request collapsing all room list sections.
|
||||
*/
|
||||
RoomListCollapseAllSections = "room_list_collapse_all_sections",
|
||||
|
||||
/**
|
||||
* Fired to request expanding all room list sections.
|
||||
*/
|
||||
RoomListExpandAllSections = "room_list_expand_all_sections",
|
||||
|
||||
/**
|
||||
* Fired to report the collapse state of a given room list section.
|
||||
* Payload: {@link RoomListSectionsCollapseStateChangedPayload}
|
||||
*/
|
||||
RoomListSectionsCollapseStateChanged = "room_list_sections_collapse_state_changed",
|
||||
}
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2026 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.
|
||||
*/
|
||||
|
||||
import { type CollapseSectionsOption } from "@element-hq/web-shared-components";
|
||||
|
||||
import { type ActionPayload } from "../payloads";
|
||||
import { type Action } from "../actions";
|
||||
|
||||
export interface RoomListSectionsCollapseStateChangedPayload extends ActionPayload {
|
||||
action: Action.RoomListSectionsCollapseStateChanged;
|
||||
/**
|
||||
* The new collapse state for the room list sections.
|
||||
* If undefined, the feature is disabled.
|
||||
*/
|
||||
collapseSections?: CollapseSectionsOption;
|
||||
}
|
||||
@ -9,14 +9,26 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { type ActionPayload } from "../payloads";
|
||||
import { type Action } from "../actions";
|
||||
import { type SettingLevel } from "../../settings/SettingLevel";
|
||||
import { type SettingValueType } from "../../settings/Settings";
|
||||
import { type SettingKey, type Settings } from "../../settings/Settings";
|
||||
|
||||
export interface SettingUpdatedPayload extends ActionPayload {
|
||||
export interface SettingUpdatedPayload<S extends SettingKey = SettingKey> extends ActionPayload {
|
||||
action: Action.SettingUpdated;
|
||||
|
||||
settingName: string;
|
||||
settingName: S;
|
||||
roomId: string | null;
|
||||
level: SettingLevel;
|
||||
newValueAtLevel: SettingLevel;
|
||||
newValue: SettingValueType;
|
||||
newValueAtLevel: Settings[S]["default"];
|
||||
newValue: Settings[S]["default"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if a payload is a SettingUpdatedPayload for a specific setting.
|
||||
* @param payload the payload to assert
|
||||
* @param settingName the setting name to check for
|
||||
*/
|
||||
export function isSettingUpdatedPayload<S extends SettingKey>(
|
||||
payload: SettingUpdatedPayload<any>,
|
||||
settingName: S,
|
||||
): payload is SettingUpdatedPayload<S> {
|
||||
return payload.settingName === settingName;
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ const STORAGE_LIMIT = 100;
|
||||
function migrate(): void {
|
||||
const data: ILegacyFormat = JSON.parse(window.localStorage.mx_reaction_count || "{}");
|
||||
const sorted = Object.entries(data).sort(([, [count1, date1]], [, [count2, date2]]) => date2 - date1);
|
||||
const newFormat = sorted.map(([emoji, [count, date]]) => [emoji, count]);
|
||||
const newFormat = sorted.map<RecentEmojiData[number]>(([emoji, [count, date]]) => [emoji, count]);
|
||||
SettingsStore.setValue(SETTING_NAME, null, SettingLevel.ACCOUNT, newFormat.slice(0, STORAGE_LIMIT));
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ export function add(emoji: string): void {
|
||||
const recents = getRecentEmoji();
|
||||
const i = recents.findIndex(([e]) => e === emoji);
|
||||
|
||||
let newEntry;
|
||||
let newEntry: RecentEmojiData[number];
|
||||
if (i >= 0) {
|
||||
// first remove the existing tuple so that we can increment it and push it to the front
|
||||
[newEntry] = recents.splice(i, 1);
|
||||
|
||||
98
apps/web/src/hooks/useUserStatus.ts
Normal file
98
apps/web/src/hooks/useUserStatus.ts
Normal file
@ -0,0 +1,98 @@
|
||||
/**
|
||||
Copyright 2026 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.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { ClientEvent, MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
import { logger as rootLogger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { useMatrixClientContext } from "../contexts/MatrixClientContext";
|
||||
import { useTypedEventEmitter } from "./useEventEmitter";
|
||||
import { useFeatureEnabled } from "./useSettings";
|
||||
|
||||
const logger = rootLogger.getChild("useUserStatus");
|
||||
|
||||
export interface UserStatus {
|
||||
emoji: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
const MAX_STATUS_TEXT_BYTES = 256;
|
||||
|
||||
export function userStatusTextWithinMaxLength(text: string): boolean {
|
||||
const textEncoder = new TextEncoder();
|
||||
return textEncoder.encode(text).length <= MAX_STATUS_TEXT_BYTES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get the MSC4426 user status for a given user ID. Returns undefined if the feature is disabled,
|
||||
* the user does not have a status, or if there was an error fetching the status.
|
||||
*
|
||||
* @param userId The ID of the user whose status is being fetched.
|
||||
* @returns The user's status, or undefined if not available.
|
||||
*/
|
||||
export function useUserStatus(userId: string | undefined): UserStatus | undefined {
|
||||
const isEnabled = useFeatureEnabled("feature_user_status");
|
||||
const matrixClient = useMatrixClientContext();
|
||||
const [rawUserStatus, setRawUserStatus] = useState<unknown>();
|
||||
|
||||
useTypedEventEmitter(matrixClient, ClientEvent.UserProfileUpdate, (syncedUserId, syncProfile) => {
|
||||
if (syncedUserId !== userId) {
|
||||
return;
|
||||
}
|
||||
if (syncProfile["org.matrix.msc4426.status"]) {
|
||||
setRawUserStatus(syncProfile["org.matrix.msc4426.status"]);
|
||||
}
|
||||
});
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
if (!userId) {
|
||||
setRawUserStatus(undefined);
|
||||
return;
|
||||
}
|
||||
if ((await matrixClient.doesServerSupportExtendedProfiles()) === false) {
|
||||
setRawUserStatus(undefined);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await matrixClient.getExtendedProfileProperty(userId, "org.matrix.msc4426.status");
|
||||
setRawUserStatus(result);
|
||||
} catch (ex) {
|
||||
if (ex instanceof MatrixError && ex.errcode === "M_NOT_FOUND") {
|
||||
setRawUserStatus(undefined);
|
||||
} else {
|
||||
logger.warn(`Failed to get userStatus for ${userId}`, ex);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [isEnabled, userId, matrixClient]);
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof rawUserStatus !== "object" || rawUserStatus === null) {
|
||||
logger.warn(`value of "org.matrix.msc4426.status" was not an object for ${userId}`);
|
||||
return;
|
||||
}
|
||||
if ("emoji" in rawUserStatus === false || typeof rawUserStatus.emoji !== "string" || !rawUserStatus.emoji) {
|
||||
logger.warn(`"emoji" property was not a valid string for ${userId}`);
|
||||
return;
|
||||
}
|
||||
if ("text" in rawUserStatus === false || typeof rawUserStatus.text !== "string" || !rawUserStatus.text) {
|
||||
logger.warn(`"text" property was not a valid string for ${userId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
emoji: rawUserStatus.emoji,
|
||||
text: userStatusTextWithinMaxLength(rawUserStatus.text)
|
||||
? rawUserStatus.text
|
||||
: `${rawUserStatus.text.slice(0, MAX_STATUS_TEXT_BYTES)}…`,
|
||||
};
|
||||
}
|
||||
@ -588,7 +588,6 @@
|
||||
"video": "Video",
|
||||
"video_room": "Video místnost",
|
||||
"view_message": "Zobrazit zprávu",
|
||||
"voice": "Hlas",
|
||||
"warning": "Upozornění"
|
||||
},
|
||||
"composer": {
|
||||
@ -3909,7 +3908,6 @@
|
||||
"connection_lost": "Došlo ke ztrátě připojení k serveru",
|
||||
"connection_lost_description": "Bez připojení k serveru nelze uskutečňovat hovory.",
|
||||
"consulting": "Konzultace s %(transferTarget)s. <a>Převod na %(transferee)s</a>",
|
||||
"decline_call": "Odmítnout",
|
||||
"default_device": "Výchozí zařízení",
|
||||
"dial": "Vytočit",
|
||||
"dialpad": "Číselník",
|
||||
@ -3960,7 +3958,6 @@
|
||||
"show_sidebar_button": "Zobrazit postranní panel",
|
||||
"silence": "Ztlumit zvonění",
|
||||
"silenced": "Oznámení ztlumena",
|
||||
"skip_lobby_toggle_option": "Připojte se ihned",
|
||||
"start_screenshare": "Začít sdílet obrazovku",
|
||||
"stop_screenshare": "Ukončit sdílení obrazovky",
|
||||
"too_many_calls": "Přiliš mnoho hovorů",
|
||||
@ -3982,7 +3979,6 @@
|
||||
"user_is_presenting": "%(sharerName)s prezentuje",
|
||||
"video_call": "Videohovor",
|
||||
"video_call_incoming": "Příchozí videohovor",
|
||||
"video_call_started": "Videohovor byl zahájen",
|
||||
"video_call_using": "Videohovor pomocí:",
|
||||
"voice_call": "Hlasový hovor",
|
||||
"voice_call_incoming": "Příchozí hlasový hovor",
|
||||
|
||||
@ -591,7 +591,6 @@
|
||||
"video": "Fideo",
|
||||
"video_room": "Ystafell fideo",
|
||||
"view_message": "Gweld neges",
|
||||
"voice": "Llais",
|
||||
"warning": "Rhybudd"
|
||||
},
|
||||
"composer": {
|
||||
@ -3872,7 +3871,6 @@
|
||||
"connection_lost": "Mae cysylltedd â'r gweinydd wedi'i golli",
|
||||
"connection_lost_description": "Allwch chi ddim osod galwadau heb gysylltiad â'r gweinydd.",
|
||||
"consulting": "Ymgynghori â %(transferTarget)s. <a>Trosglwyddo i %(transferee)s</a>",
|
||||
"decline_call": "Gwrthod",
|
||||
"default_device": "Dyfais Rhagosodedig",
|
||||
"dial": "Deial",
|
||||
"dialpad": "Pad Deialu",
|
||||
@ -3923,7 +3921,6 @@
|
||||
"show_sidebar_button": "Dangos y bar ochr",
|
||||
"silence": "Distewi galwad",
|
||||
"silenced": "Hysbysiadau wedi'u distewi",
|
||||
"skip_lobby_toggle_option": "Ymunwch ar unwaith",
|
||||
"start_screenshare": "Dechreuwch rannu'ch sgrin",
|
||||
"stop_screenshare": "Stopiwch rannu'ch sgrin",
|
||||
"too_many_calls": "Gormod o Alwadau",
|
||||
@ -3945,7 +3942,6 @@
|
||||
"user_is_presenting": "Mae %(sharerName)s yn cyflwyno",
|
||||
"video_call": "Galwad fideo",
|
||||
"video_call_incoming": "Galwad fideo i mewn",
|
||||
"video_call_started": "Galwad fideo wedi dechrau",
|
||||
"video_call_using": "Galwad fideo gan ddefnyddio:",
|
||||
"voice_call": "Galwad llais",
|
||||
"voice_call_incoming": "Galwad llais i mewn",
|
||||
|
||||
@ -3412,7 +3412,6 @@
|
||||
"connection_lost": "Forbindelsen til serveren er tabt",
|
||||
"connection_lost_description": "Du kan ikke lave et opkald uden en forbindelse til serveren.",
|
||||
"consulting": "Konfererer med %(transferTarget)s. <a> Overfør til %(transferee)s</a>",
|
||||
"decline_call": "Afvis",
|
||||
"default_device": "Standardenhed",
|
||||
"dial": "Ring",
|
||||
"dialpad": "Tastatur",
|
||||
@ -3480,7 +3479,6 @@
|
||||
"user_busy_description": "Brugeren du ringede til er optaget.",
|
||||
"user_is_presenting": "%(sharerName)s præsenterer",
|
||||
"video_call": "Videoopkald",
|
||||
"video_call_started": "Videoopkald startet",
|
||||
"video_call_using": "Videoopkald med:",
|
||||
"voice_call": "Stemmeopkald",
|
||||
"you_are_presenting": "Du præsenterer"
|
||||
|
||||
@ -588,7 +588,6 @@
|
||||
"video": "Video",
|
||||
"video_room": "Videochat",
|
||||
"view_message": "Nachricht anzeigen",
|
||||
"voice": "Sprachanruf",
|
||||
"warning": "Warnung"
|
||||
},
|
||||
"composer": {
|
||||
@ -3865,7 +3864,6 @@
|
||||
"connection_lost": "Verbindung zum Server unterbrochen",
|
||||
"connection_lost_description": "Du kannst ohne Verbindung zum Server keine Anrufe tätigen.",
|
||||
"consulting": "%(transferTarget)s wird angefragt. <a>Übertragung zu %(transferee)s</a>",
|
||||
"decline_call": "Ablehnen",
|
||||
"default_device": "Standardgerät",
|
||||
"dial": "Wählen",
|
||||
"dialpad": "Telefontastatur",
|
||||
@ -3916,7 +3914,6 @@
|
||||
"show_sidebar_button": "Seitenleiste anzeigen",
|
||||
"silence": "Anruf stummschalten",
|
||||
"silenced": "Benachrichtigungen stummgeschaltet",
|
||||
"skip_lobby_toggle_option": "Sofort beitreten",
|
||||
"start_screenshare": "Bildschirmfreigabe starten",
|
||||
"stop_screenshare": "Bildschirmfreigabe beenden",
|
||||
"too_many_calls": "Zu viele Anrufe",
|
||||
@ -3938,7 +3935,6 @@
|
||||
"user_is_presenting": "%(sharerName)s präsentiert",
|
||||
"video_call": "Videoanruf",
|
||||
"video_call_incoming": "Eingehender Videoanruf",
|
||||
"video_call_started": "Videoanruf hat begonnen",
|
||||
"video_call_using": "Videoanruf mit:",
|
||||
"voice_call": "Sprachanruf",
|
||||
"voice_call_incoming": "Eingehender Anruf",
|
||||
|
||||
@ -3164,7 +3164,6 @@
|
||||
"user_busy_description": "Ο χρήστης που καλέσατε είναι απασχολημένος.",
|
||||
"user_is_presenting": "%(sharerName)s παρουσιάζει",
|
||||
"video_call": "Βιντεοκλήση",
|
||||
"video_call_started": "Ξεκίνησε η βιντεοκλήση",
|
||||
"voice_call": "Φωνητική κλήση",
|
||||
"you_are_presenting": "Παρουσιάζετε"
|
||||
},
|
||||
|
||||
@ -476,6 +476,7 @@
|
||||
"description": "Description",
|
||||
"deselect_all": "Deselect all",
|
||||
"device": "Device",
|
||||
"disabled_by_homeserver": "Disabled by homeserver",
|
||||
"edited": "edited",
|
||||
"email_address": "Email address",
|
||||
"emoji": "Emoji",
|
||||
@ -1540,6 +1541,11 @@
|
||||
"experimental_section": "Early previews",
|
||||
"extended_profiles_msc_support": "Requires your server to support MSC4133",
|
||||
"feature_disable_call_per_sender_encryption": "Disable per-sender encryption for Element Call",
|
||||
"feature_user_status": {
|
||||
"description": "Enables being able to see and set a current status.",
|
||||
"display_name": "User status",
|
||||
"required_msc_support": "Requires MSC4429 (Profile Updates for Legacy Sync)"
|
||||
},
|
||||
"feature_wysiwyg_composer_description": "Use rich text instead of Markdown in the message composer.",
|
||||
"group_calls": "New group call experience",
|
||||
"group_developer": "Developer",
|
||||
@ -3147,6 +3153,14 @@
|
||||
"server_error_detail": "Server unavailable, overloaded, or something else went wrong.",
|
||||
"shrug": "Prepends ¯\\_(ツ)_/¯ to a plain-text message",
|
||||
"spoiler": "Sends the given message as a spoiler",
|
||||
"status": {
|
||||
"description": "Set your current status",
|
||||
"no_args": "No arguments provided. You should supply an emoij and an optional text component.",
|
||||
"no_emoji": "You did not provide an emoji",
|
||||
"no_text": "You did not provide any status text",
|
||||
"too_long_emoji": "The first argument must be an emoji",
|
||||
"too_long_text": "The text you provided was too long."
|
||||
},
|
||||
"tableflip": "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message",
|
||||
"topic": "Gets or sets the room topic",
|
||||
"topic_none": "This room has no topic.",
|
||||
|
||||
@ -2485,7 +2485,6 @@
|
||||
"user_busy_description": "La uzanto, kiun vi vokis, estas okupata.",
|
||||
"user_is_presenting": "%(sharerName)s prezentas",
|
||||
"video_call": "Vidvoko",
|
||||
"video_call_started": "Videovoko komenciĝis",
|
||||
"voice_call": "Voĉvoko",
|
||||
"you_are_presenting": "Vi prezentas"
|
||||
},
|
||||
|
||||
@ -3250,7 +3250,6 @@
|
||||
"user_busy_description": "La persona a la que has llamado está ocupada.",
|
||||
"user_is_presenting": "%(sharerName)s está presentando",
|
||||
"video_call": "Llamada de vídeo",
|
||||
"video_call_started": "Videollamada iniciada",
|
||||
"voice_call": "Llamada de voz",
|
||||
"you_are_presenting": "Estás presentando"
|
||||
},
|
||||
|
||||
@ -588,7 +588,6 @@
|
||||
"video": "Video",
|
||||
"video_room": "Videotuba",
|
||||
"view_message": "Vaata sõnumit",
|
||||
"voice": "Hääl",
|
||||
"warning": "Hoiatus"
|
||||
},
|
||||
"composer": {
|
||||
@ -3865,7 +3864,6 @@
|
||||
"connection_lost": "Ühendus sinu serveriga on katkenud",
|
||||
"connection_lost_description": "Kui ühendus sinu serveriga on katkenud, siis sa ei saa helistada.",
|
||||
"consulting": "Suhtlen teise osapoolega %(transferTarget)s. <a>Saadan andmeid kasutajale %(transferee)s</a>",
|
||||
"decline_call": "Keeldu",
|
||||
"default_device": "Vaikimisi seade",
|
||||
"dial": "Helista",
|
||||
"dialpad": "Numbriklahvistik",
|
||||
@ -3916,7 +3914,6 @@
|
||||
"show_sidebar_button": "Näita külgpaani",
|
||||
"silence": "Vaigista kõne",
|
||||
"silenced": "Teavitused on summutatud",
|
||||
"skip_lobby_toggle_option": "Liitu kohe",
|
||||
"start_screenshare": "Alusta oma seadme ekraani jagamist",
|
||||
"stop_screenshare": "Lõpeta oma seadme ekraani jagamine",
|
||||
"too_many_calls": "Liiga palju kõnesid",
|
||||
@ -3938,7 +3935,6 @@
|
||||
"user_is_presenting": "%(sharerName)s esitab",
|
||||
"video_call": "Videokõne",
|
||||
"video_call_incoming": "Saabuv videokõne",
|
||||
"video_call_started": "Videokõne algas",
|
||||
"video_call_using": "Videokõne, kus on kasutusel:",
|
||||
"voice_call": "Häälkõne",
|
||||
"voice_call_incoming": "Saaduv häälkõne",
|
||||
|
||||
@ -2187,7 +2187,6 @@
|
||||
"user_busy": "کاربر مشغول",
|
||||
"user_busy_description": "کاربر موردنظر مشغول است.",
|
||||
"video_call": "تماس تصویری",
|
||||
"video_call_started": "تماس تصویری شروع شد",
|
||||
"voice_call": "تماس صوتی"
|
||||
},
|
||||
"web_default_device_name": "%(appName)s: %(browserName)s: روی %(osName)s",
|
||||
|
||||
@ -3364,7 +3364,6 @@
|
||||
"user_busy_description": "Käyttäjä, jolle soitit, on varattu.",
|
||||
"user_is_presenting": "%(sharerName)s esittää",
|
||||
"video_call": "Videopuhelu",
|
||||
"video_call_started": "Videopuhelu aloitettu",
|
||||
"video_call_using": "Videopuhelu käyttäen:",
|
||||
"voice_call": "Äänipuhelu",
|
||||
"you_are_presenting": "Esität parhaillaan"
|
||||
|
||||
@ -588,7 +588,6 @@
|
||||
"video": "Vidéo",
|
||||
"video_room": "Salon vidéo",
|
||||
"view_message": "Afficher le message",
|
||||
"voice": "Voix",
|
||||
"warning": "Attention"
|
||||
},
|
||||
"composer": {
|
||||
@ -1368,6 +1367,14 @@
|
||||
"impossible_dialog_title": "Les intégrations ne sont pas autorisées"
|
||||
},
|
||||
"invite": {
|
||||
"confirm_unknown_users": {
|
||||
"invite_subtitle": "Vous n'avez actuellement aucune discussion avec ces contacts. Veuillez confirmer leur invitation à rejoindre ce salon avant de continuer.",
|
||||
"invite_title": "Inviter de nouveaux contacts dans ce salon ?",
|
||||
"start_chat_subtitle_multiple_users": "Vous n'avez actuellement aucune discussion avec ces personnes. Veuillez confirmer leur invitation avant de continuer.",
|
||||
"start_chat_subtitle_one_user": "Vous n'avez actuellement aucune discussion avec cette personne. Confirmez son invitation avant de continuer.",
|
||||
"start_chat_title_multiple_users": "Démarrer une discussion avec ces nouveaux contacts ?",
|
||||
"start_chat_title_one_user": "Démarrer une discussion avec ce nouveau contact ?"
|
||||
},
|
||||
"email_caption": "Inviter par e-mail",
|
||||
"email_limit_one": "Les invitations par e-mail ne peuvent être envoyées qu’une par une",
|
||||
"email_use_default_is": "Utilisez un serveur d’identité pour inviter avec un e-mail. <default>Utilisez le serveur par défaut (%(defaultIdentityServerName)s)</default> ou gérez-le dans les <settings>Paramètres</settings>.",
|
||||
@ -3886,6 +3893,16 @@
|
||||
"call_held": "%(peerName)s a mis l’appel en attente",
|
||||
"call_held_resume": "Vous avez mis l’appel en attente <a>Reprendre</a>",
|
||||
"call_held_switch": "Vous avez mis l’appel en attente <a>Basculer</a>",
|
||||
"call_members": {
|
||||
"exhaustive": {
|
||||
"one": "<avatars/> participant à l'appel",
|
||||
"other": "<avatars/> participants à l'appel"
|
||||
},
|
||||
"overflow": {
|
||||
"one": "<avatars/>+ %(overflowCount)s participant à l'appel",
|
||||
"other": "<avatars/>+ %(overflowCount)s participants à l'appel"
|
||||
}
|
||||
},
|
||||
"call_toast_unknown_room": "Salon inconnu",
|
||||
"camera_disabled": "Votre caméra est éteinte",
|
||||
"camera_enabled": "Votre caméra est toujours allumée",
|
||||
@ -3895,7 +3912,6 @@
|
||||
"connection_lost": "La connexion au serveur a été perdue",
|
||||
"connection_lost_description": "Vous ne pouvez pas passer d’appels sans connexion au serveur.",
|
||||
"consulting": "Consultation avec %(transferTarget)s. <a>Transfert à %(transferee)s</a>",
|
||||
"decline_call": "Refuser",
|
||||
"default_device": "Appareil par défaut",
|
||||
"dial": "Composer",
|
||||
"dialpad": "Pavé numérique",
|
||||
@ -3909,10 +3925,12 @@
|
||||
"enable_microphone": "Activer le microphone",
|
||||
"expand": "Revenir à l’appel",
|
||||
"get_call_link": "Partager le lien de l'appel",
|
||||
"group_call_started": "L'appel de groupe a commencé",
|
||||
"hangup": "Raccrocher",
|
||||
"hide_sidebar_button": "Masquer la barre latérale",
|
||||
"input_devices": "Périphériques d’entrée",
|
||||
"jitsi_call": "Conférence Jitsi",
|
||||
"join_with_video": "Rejoigner en vidéo",
|
||||
"legacy_call": "Appel vidéo",
|
||||
"maximise": "Remplir l’écran",
|
||||
"maximise_call": "Plein écran",
|
||||
@ -3946,7 +3964,6 @@
|
||||
"show_sidebar_button": "Afficher la barre latérale",
|
||||
"silence": "Mettre l’appel en sourdine",
|
||||
"silenced": "Notifications silencieuses",
|
||||
"skip_lobby_toggle_option": "Rejoignez immédiatement",
|
||||
"start_screenshare": "Commencer à partager mon écran",
|
||||
"stop_screenshare": "Arrêter de partager mon écran",
|
||||
"too_many_calls": "Trop d’appels",
|
||||
@ -3968,7 +3985,6 @@
|
||||
"user_is_presenting": "%(sharerName)s est à l’écran",
|
||||
"video_call": "Appel vidéo",
|
||||
"video_call_incoming": "Appel vidéo entrant",
|
||||
"video_call_started": "Appel vidéo commencé",
|
||||
"video_call_using": "Appel vidéo utilisant :",
|
||||
"voice_call": "Appel audio",
|
||||
"voice_call_incoming": "Appel vocal entrant",
|
||||
@ -3976,6 +3992,11 @@
|
||||
"you_are_presenting": "Vous êtes à l’écran"
|
||||
},
|
||||
"web_default_device_name": "%(appName)s : %(browserName)s pour %(osName)s",
|
||||
"welcome": {
|
||||
"tagline_element": "Conçu pour la vitesse et la simplicité.",
|
||||
"title_element": "Soyez dans votre Element",
|
||||
"title_generic": "Bienvenue sur %(brand)s"
|
||||
},
|
||||
"widget": {
|
||||
"added_by": "Widget ajouté par",
|
||||
"capabilities_dialog": {
|
||||
|
||||
@ -593,7 +593,6 @@
|
||||
"video": "Videozapis",
|
||||
"video_room": "Videosoba",
|
||||
"view_message": "Prikaži poruku",
|
||||
"voice": "Glas",
|
||||
"warning": "Upozorenje"
|
||||
},
|
||||
"composer": {
|
||||
@ -3951,7 +3950,6 @@
|
||||
"connection_lost": "Veza s poslužiteljem je prekinuta",
|
||||
"connection_lost_description": "Ne možete upućivati pozive ako niste povezani s poslužiteljem.",
|
||||
"consulting": "Savjetovanje s %(transferTarget)s. <a>Prijenos na %(transferee)s</a>",
|
||||
"decline_call": "Odbij",
|
||||
"default_device": "Zadani uređaj",
|
||||
"dial": "Biranje",
|
||||
"dialpad": "Brojčanik",
|
||||
@ -4003,7 +4001,6 @@
|
||||
"show_sidebar_button": "Prikaži bočnu traku",
|
||||
"silence": "Utišaj poziv",
|
||||
"silenced": "Obavijesti su utišane",
|
||||
"skip_lobby_toggle_option": "Pridruži se odmah",
|
||||
"start_screenshare": "Započni dijeljenje zaslona",
|
||||
"stop_screenshare": "Zaustavi dijeljenje zaslona",
|
||||
"too_many_calls": "Previše poziva",
|
||||
@ -4025,7 +4022,6 @@
|
||||
"user_is_presenting": "%(sharerName)s dijeli zaslon",
|
||||
"video_call": "Videopoziv",
|
||||
"video_call_incoming": "Dolazni videopoziv",
|
||||
"video_call_started": "Videopoziv je započeo",
|
||||
"video_call_using": "Videopoziv putem:",
|
||||
"voice_call": "Glasovni poziv",
|
||||
"voice_call_incoming": "Dolazni glasovni poziv",
|
||||
|
||||
@ -586,7 +586,6 @@
|
||||
"video": "Videó",
|
||||
"video_room": "Videószoba",
|
||||
"view_message": "Üzenet megjelenítése",
|
||||
"voice": "Hang",
|
||||
"warning": "Figyelmeztetés"
|
||||
},
|
||||
"composer": {
|
||||
@ -3860,7 +3859,6 @@
|
||||
"connection_lost": "Megszakadt a kapcsolat a kiszolgálóval",
|
||||
"connection_lost_description": "Nem kezdeményezhet hívást a kiszolgálóval való kapcsolat nélkül.",
|
||||
"consulting": "Egyeztetés vele: %(transferTarget)s. <a>Átadás ide: %(transferee)s</a>",
|
||||
"decline_call": "Elutasítás",
|
||||
"default_device": "Alapértelmezett eszköz",
|
||||
"dial": "Tárcsázás",
|
||||
"dialpad": "Tárcsázó",
|
||||
@ -3910,7 +3908,6 @@
|
||||
"show_sidebar_button": "Oldalsáv megjelenítése",
|
||||
"silence": "Hívás némítása",
|
||||
"silenced": "Értesítések némítva",
|
||||
"skip_lobby_toggle_option": "Csatlakozás azonnal",
|
||||
"start_screenshare": "Képernyőmegosztás bekapcsolása",
|
||||
"stop_screenshare": "Képernyőmegosztás kikapcsolása",
|
||||
"too_many_calls": "Túl sok hívás",
|
||||
@ -3932,7 +3929,6 @@
|
||||
"user_is_presenting": "%(sharerName)s tartja a bemutatót",
|
||||
"video_call": "Videóhívás",
|
||||
"video_call_incoming": "Bejövő videóhívás",
|
||||
"video_call_started": "A videóhívás elindult",
|
||||
"video_call_using": "Videóhívás:",
|
||||
"voice_call": "Hanghívás",
|
||||
"voice_call_incoming": "Bejövő hanghívás",
|
||||
|
||||
@ -3839,7 +3839,6 @@
|
||||
"user_busy_description": "Ձեր զանգահարած օգտատերը զբաղված է։",
|
||||
"user_is_presenting": "%(sharerName)s-ը ներկայացնում է",
|
||||
"video_call": "Տեսազանգ",
|
||||
"video_call_started": "Տեսազանգը սկսվեց",
|
||||
"video_call_using": "Տեսազանգ՝ օգտագործելով՝",
|
||||
"voice_call": "Ձայնային զանգ",
|
||||
"you_are_presenting": "Դուք ներկայացնում եք"
|
||||
|
||||
@ -588,7 +588,6 @@
|
||||
"video": "Video",
|
||||
"video_room": "Ruangan video",
|
||||
"view_message": "Tampilkan pesan",
|
||||
"voice": "Suara",
|
||||
"warning": "Peringatan"
|
||||
},
|
||||
"composer": {
|
||||
@ -3852,7 +3851,6 @@
|
||||
"connection_lost": "Koneksi ke server telah hilang",
|
||||
"connection_lost_description": "Anda tidak dapat membuat panggilan tanpa terhubung ke server.",
|
||||
"consulting": "Mengkonsultasi dengan %(transferTarget)s. <a>Transfer ke %(transferee)s</a>",
|
||||
"decline_call": "Tolak",
|
||||
"default_device": "Perangkat Bawaan",
|
||||
"dial": "Panggil",
|
||||
"dialpad": "Tombol Penyetel",
|
||||
@ -3903,7 +3901,6 @@
|
||||
"show_sidebar_button": "Tampilkan sisi bilah",
|
||||
"silence": "Diamkan panggilan",
|
||||
"silenced": "Notifikasi dibisukan",
|
||||
"skip_lobby_toggle_option": "Segera bergabung",
|
||||
"start_screenshare": "Mulai membagikan layar Anda",
|
||||
"stop_screenshare": "Berhenti membagikan layar Anda",
|
||||
"too_many_calls": "Terlalu Banyak Panggilan",
|
||||
@ -3925,7 +3922,6 @@
|
||||
"user_is_presenting": "%(sharerName)s sedang mempresentasi",
|
||||
"video_call": "Panggilan video",
|
||||
"video_call_incoming": "Panggilan video masuk",
|
||||
"video_call_started": "Panggilan video dimulai",
|
||||
"video_call_using": "Panggilan video menggunakan:",
|
||||
"voice_call": "Panggilan suara",
|
||||
"voice_call_incoming": "Panggilan suara masuk",
|
||||
|
||||
@ -2824,7 +2824,6 @@
|
||||
"user_busy_description": "Notandinn sem þú hringdir í er upptekinn.",
|
||||
"user_is_presenting": "%(sharerName)s er að kynna",
|
||||
"video_call": "Myndsímtal",
|
||||
"video_call_started": "Myndsímtal er byrjað",
|
||||
"voice_call": "Raddsímtal",
|
||||
"you_are_presenting": "Þú ert að kynna"
|
||||
},
|
||||
|
||||
@ -3412,7 +3412,6 @@
|
||||
"user_busy_description": "L'utente che hai chiamato è occupato.",
|
||||
"user_is_presenting": "%(sharerName)s sta presentando",
|
||||
"video_call": "Videochiamata",
|
||||
"video_call_started": "Videochiamata iniziata",
|
||||
"video_call_using": "Videochiamata usando:",
|
||||
"voice_call": "Telefonata",
|
||||
"you_are_presenting": "Stai presentando"
|
||||
|
||||
@ -3136,7 +3136,6 @@
|
||||
"user_busy_description": "呼び出したユーザーは通話中です。",
|
||||
"user_is_presenting": "%(sharerName)sが画面を共有しています",
|
||||
"video_call": "ビデオ通話",
|
||||
"video_call_started": "ビデオ通話を開始しました",
|
||||
"voice_call": "音声通話",
|
||||
"you_are_presenting": "あなたが画面を共有しています"
|
||||
},
|
||||
|
||||
@ -2669,7 +2669,6 @@
|
||||
"user_busy_description": "მომხმარებელი, რომელსაც დაურეკე, დაკავებულია.",
|
||||
"user_is_presenting": "%(sharerName)sწარმოადგენს",
|
||||
"video_call": "ვიდეო ზარი",
|
||||
"video_call_started": "ვიდეო ზარი დაიწყო",
|
||||
"voice_call": "ხმოვანი ზარი",
|
||||
"you_are_presenting": "წარმოგიდგენთ"
|
||||
},
|
||||
|
||||
@ -583,7 +583,6 @@
|
||||
"video": "비디오",
|
||||
"video_room": "비디오 room",
|
||||
"view_message": "메시지 보기",
|
||||
"voice": "음성",
|
||||
"warning": "경고"
|
||||
},
|
||||
"composer": {
|
||||
@ -3785,7 +3784,6 @@
|
||||
"connection_lost": "서버와의 연결이 끊어졌습니다.",
|
||||
"connection_lost_description": "서버에 연결되지 않으면 전화를 걸 수 없습니다.",
|
||||
"consulting": "%(transferTarget)s와 상담 중입니다. <a>%(transferee)s에게 통화 이전</a>",
|
||||
"decline_call": "거부",
|
||||
"default_device": "기본 기기",
|
||||
"dial": "전화걸기",
|
||||
"dialpad": "다이얼패드",
|
||||
@ -3835,7 +3833,6 @@
|
||||
"show_sidebar_button": "사이드바 표시",
|
||||
"silence": "통화 음소거",
|
||||
"silenced": "알림 음소거됨",
|
||||
"skip_lobby_toggle_option": "즉시 가입하세요",
|
||||
"start_screenshare": "화면 공유를 시작하세요",
|
||||
"stop_screenshare": "화면 공유 중지",
|
||||
"too_many_calls": "전화가 너무 많아요",
|
||||
@ -3857,7 +3854,6 @@
|
||||
"user_is_presenting": "%(sharerName)s님이 화면을 공유중입니다.",
|
||||
"video_call": "영상 통화",
|
||||
"video_call_incoming": "수신 영상 통화",
|
||||
"video_call_started": "영상통화가 시작되었습니다",
|
||||
"video_call_using": "사용 중인 영상 통화:",
|
||||
"voice_call": "음성 통화",
|
||||
"voice_call_incoming": "수신 음성 통화",
|
||||
|
||||
@ -94,7 +94,7 @@
|
||||
"show_advanced": "Rodyti išplėstinius",
|
||||
"show_all": "Rodyti viską",
|
||||
"sign_in": "Prisijungti",
|
||||
"sign_out": "Atsijungti",
|
||||
"sign_out": "Šalinti šį įrenginį",
|
||||
"skip": "Praleisti",
|
||||
"start": "Pradėti",
|
||||
"start_chat": "Pradėti pokalbį",
|
||||
@ -208,7 +208,7 @@
|
||||
"set_email": {
|
||||
"description": "Tai jums leis iš naujo nustatyti slaptažodį ir gauti pranešimus.",
|
||||
"verification_pending_description": "Patikrinkite savo el. laišką ir spustelėkite jame esančią nuorodą. Kai tai padarysite, spauskite tęsti.",
|
||||
"verification_pending_title": "Laukiama Patikrinimo"
|
||||
"verification_pending_title": "Laukiama patvirtinimo"
|
||||
},
|
||||
"set_email_prompt": "Ar norite nustatyti el. pašto adresą?",
|
||||
"sign_in_instead_prompt": "Jau turite paskyrą? <a>Prisijunkite čia</a>",
|
||||
@ -402,8 +402,13 @@
|
||||
"format_code_block": "Kodo blokas",
|
||||
"format_inline_code": "Kodas",
|
||||
"format_insert_link": "Įterpti nuorodą",
|
||||
"format_italic": "Kursyvas",
|
||||
"format_italics": "Kursyvas",
|
||||
"format_link": "Nuoroda",
|
||||
"format_ordered_list": "Sunumeruotas sąrašas",
|
||||
"format_strikethrough": "Perbrauktas",
|
||||
"format_underline": "Pabraukimas",
|
||||
"format_unordered_list": "Suženklintasis sąrašas",
|
||||
"no_perms_notice": "Jūs neturite leidimų rašyti šiame kambaryje",
|
||||
"placeholder": "Siųsti žinutę…",
|
||||
"placeholder_encrypted": "Siųsti šifruotą žinutę…",
|
||||
@ -424,6 +429,8 @@
|
||||
"voice_message_button": "Balso žinutė"
|
||||
},
|
||||
"create_room": {
|
||||
"action_create_room": "Kurti kambarį",
|
||||
"action_create_video_room": "Kurti vaizdo kambarį",
|
||||
"encryption_forced": "Jūsų serveris reikalauja, kad šifravimas būtų įjungtas privačiuose kambariuose.",
|
||||
"encryption_label": "Įjungti visapusį šifravimą",
|
||||
"error_title": "Nepavyko sukurti kambario",
|
||||
@ -431,6 +438,7 @@
|
||||
"name_validation_required": "Įveskite kambario pavadinimą",
|
||||
"title_private_room": "Sukurti privatų kambarį",
|
||||
"title_public_room": "Sukurti viešą kambarį",
|
||||
"title_video_room": "Kurti vaizdo pokalbių kambarį",
|
||||
"topic_label": "Tema (nebūtina)",
|
||||
"unfederated": "Blokuoti bet ką, kas nėra iš %(serverName)s, niekada nebeleidžiant prisijungti prie šio kambario.",
|
||||
"unfederated_label_default_off": "Jūs galite tai įjungti, jei kambarys bus naudojamas tik bendradarbiavimui su vidinėmis komandomis jūsų serveryje. Tai negali būti vėliau pakeista.",
|
||||
@ -461,16 +469,33 @@
|
||||
"event_content": "Įvykio turinys",
|
||||
"event_sent": "Įvykis išsiųstas!",
|
||||
"event_type": "Įvykio tipas",
|
||||
"explore_account_data": "Naršyti paskyros duomenis",
|
||||
"explore_room_account_data": "Naršyti kambario paskyros duomenis",
|
||||
"explore_room_state": "Naršyti kambario būseną",
|
||||
"failed_to_find_widget": "Įvyko klaida ieškant šio valdiklio.",
|
||||
"failed_to_send": "Nepavyko išsiųsti įvykio.",
|
||||
"invalid_json": "Neatrodo kaip tinkamas JSON.",
|
||||
"level": "Lygis",
|
||||
"no_receipt_found": "Nerasta kvito.",
|
||||
"notifications_debug": "Pranešimų derinimas",
|
||||
"original_event_source": "Originalus įvykio šaltinis",
|
||||
"room_id": "Kambario ID: %(roomId)s",
|
||||
"send_custom_account_data_event": "Siųsti pasirinktinius paskyros duomenis įvykį",
|
||||
"send_custom_room_account_data_event": "Siųsti pasirinktinius kambario paskyros duomenis įvykį",
|
||||
"send_custom_timeline_event": "Siųsti pasirinktinės laiko skalės įvykį",
|
||||
"server_info": "Serverio informacija",
|
||||
"setting_colon": "Nustatymas:",
|
||||
"setting_id": "Nustatymo ID",
|
||||
"settings_explorer": "Nustatymų naršyklė",
|
||||
"show_hidden_events": "Rodyti paslėptus įvykius laiko juostoje",
|
||||
"state_key": "Būklės raktas",
|
||||
"thread_root_id": "Gijos šaknies ID: %(threadRootId)s",
|
||||
"title": "Kūrėjo įrankiai",
|
||||
"toolbox": "Įrankinė",
|
||||
"user_read_up_to": "Naudotojas perskaitė iki: ",
|
||||
"user_read_up_to_ignore_synthetic": "Naudotojas perskaitė iki (ignoreSynthetic): ",
|
||||
"value": "Reikšmė",
|
||||
"view_servers_in_room": "Peržiūrėti serverius kambaryje",
|
||||
"widget_screenshots": "Įjungti valdiklių ekrano kopijas palaikomuose valdikliuose"
|
||||
},
|
||||
"dialog_close_label": "Uždaryti dialogą",
|
||||
@ -581,7 +606,7 @@
|
||||
"unverified_session_toast_title": "Naujas prisijungimas. Ar tai jūs?",
|
||||
"unverified_sessions_toast_description": "Peržiūrėkite, ar jūsų paskyra yra saugi",
|
||||
"unverified_sessions_toast_reject": "Vėliau",
|
||||
"verification_dialog_title_user": "Patikrinimo Užklausa",
|
||||
"verification_dialog_title_user": "Patvirtinimo prašymas",
|
||||
"verify_emoji": "Patvirtinti naudojant jaustukus",
|
||||
"verify_emoji_prompt": "Patvirtinti palyginant unikalius jaustukus.",
|
||||
"verify_emoji_prompt_qr": "Jei nuskaityti aukščiau esančio kodo negalite, patvirtinkite palygindami unikalius jaustukus.",
|
||||
@ -591,7 +616,8 @@
|
||||
"waiting_other_user": "Laukiama kol %(displayName)s patvirtins…"
|
||||
},
|
||||
"verify_toast_description": "Kiti vartotojai gali nepasitikėti",
|
||||
"verify_toast_title": "Patvirtinti šį seansą"
|
||||
"verify_toast_title": "Patvirtinti šį seansą",
|
||||
"withdraw_verification_action": "Atšaukti patvirtinimą"
|
||||
},
|
||||
"error": {
|
||||
"admin_contact_short": "Susisiekite su savo <a>serverio administratoriumi</a>.",
|
||||
@ -798,7 +824,9 @@
|
||||
},
|
||||
"keyboard": {
|
||||
"activate_button": "Aktyvuoti pasirinktą mygtuką",
|
||||
"alt": "Alt",
|
||||
"autocomplete_cancel": "Atšaukti automatinį užbaigimą",
|
||||
"backspace": "Naikinimo klavišas",
|
||||
"cancel_reply": "Atšaukti atsakymą į žinutę",
|
||||
"category_autocomplete": "Autorašymas",
|
||||
"category_calls": "Skambučiai",
|
||||
@ -809,29 +837,42 @@
|
||||
"composer_toggle_bold": "Perjungti paryškinimą",
|
||||
"composer_toggle_italics": "Perjungti kursyvą",
|
||||
"composer_toggle_quote": "Perjungti citatą",
|
||||
"control": "Vald",
|
||||
"dismiss_read_marker_and_jump_bottom": "Atsisakyti skaitymo žymeklio ir nušokti į apačią",
|
||||
"end": "Pab",
|
||||
"enter": "Įvesti",
|
||||
"escape": "Gr",
|
||||
"home": "Pradžia",
|
||||
"jump_room_search": "Nušokti į kambarių paiešką",
|
||||
"jump_to_read_marker": "Nušokti iki seniausios neperskaitytos žinutės",
|
||||
"number": "[skaičius]",
|
||||
"page_down": "Puslapis žemyn",
|
||||
"page_up": "Puslapis aukštyn",
|
||||
"room_list_collapse_section": "Sutraukti kambarių sąrašo skyrių",
|
||||
"room_list_expand_section": "Išplėsti kambarių sąrašo skyrių",
|
||||
"room_list_select_room": "Pasirinkti kambarį iš kambarių sąrašo",
|
||||
"search": "Paieška (turi būti įjungta)",
|
||||
"shift": "Lyg2",
|
||||
"space": "Tarpas",
|
||||
"toggle_microphone_mute": "Perjungti mikrofono nutildymą",
|
||||
"toggle_right_panel": "Perjungti dešinį skydelį",
|
||||
"toggle_top_left_menu": "Perjungti viršutinį kairės pusės meniu",
|
||||
"upload_file": "Įkelti failą"
|
||||
},
|
||||
"labs": {
|
||||
"ask_to_join": "Įjungti prašymą jungtis",
|
||||
"beta_feedback_leave_button": "Norėdami išeiti iš beta versijos, apsilankykite savo nustatymuose.",
|
||||
"bridge_state": "Rodyti informaciją apie tiltus kambario nustatymuose",
|
||||
"bridge_state_channel": "Kanalas: <channelLink/>",
|
||||
"bridge_state_creator": "Šis tiltas buvo parūpintas <user />.",
|
||||
"bridge_state_manager": "Šis tiltas yra tvarkomas <user />.",
|
||||
"bridge_state_workspace": "Darbo aplinka: <networkLink/>",
|
||||
"currently_experimental": "Šiuo metu eksperimentinė.",
|
||||
"custom_themes": "Palaikykite pridėdami pasirinktines temas",
|
||||
"dynamic_room_predecessors": "Dinaminiai kambario pirmtakai",
|
||||
"element_call_video_rooms": "Element skambučio vaizdo kambariai",
|
||||
"feature_wysiwyg_composer_description": "Naudoti raiškųjį tekstą vietoj ženklinimo žinučių rengyklėje.",
|
||||
"group_calls": "Nauja grupinio skambučio patirtis",
|
||||
"group_developer": "Kūrėjas",
|
||||
"group_encryption": "Šifravimas",
|
||||
"group_experimental": "Eksperimentinis",
|
||||
@ -843,13 +884,21 @@
|
||||
"group_themes": "Temos",
|
||||
"group_voip": "Garsas ir Vaizdas",
|
||||
"group_widgets": "Valdikliai",
|
||||
"hidebold": "Slėpti pranešimų tašką (rodyti tik skaičių žymes)",
|
||||
"html_topic": "Rodyti kambarių temų HTML atvaizdavimą",
|
||||
"jump_to_date": "Pereiti prie datos (prideda /jumptodate ir perėjimo prie datos antraštes)",
|
||||
"jump_to_date_msc_support": "Privaloma, kad jūsų serveris palaikytų MSC3030.",
|
||||
"latex_maths": "Atvaizduoti LaTeX matematikas žinutėse",
|
||||
"leave_beta": "Palikti beta versiją",
|
||||
"location_share_live": "Tiesioginis vietos bendrinimas",
|
||||
"location_share_live_description": "Laikinas įgyvendinimas. Vietos išlieka kambario istorijoje.",
|
||||
"mjolnir": "Nauji būdai ignoruoti žmones",
|
||||
"msc3531_hide_messages_pending_moderation": "Leisti moderatoriams slėpti žinutes, laukiančias moderavimo.",
|
||||
"notification_settings": "Nauji pranešimų nustatymai",
|
||||
"report_to_moderators": "Pranešti prižiūrėtojams",
|
||||
"sliding_sync": "Slankiojo sinchronizavimo režimas",
|
||||
"sliding_sync_description": "Aktyviai kuriama, negalima išjungti. Šiuo metu nesuderinama su „Element“ skambučiais.",
|
||||
"under_active_development": "Šiuo metu aktyviai kuriama.",
|
||||
"video_rooms": "Vaizdo kambariai",
|
||||
"video_rooms_a_new_way_to_chat": "Naujas būdas kalbėtis balsu ir vaizdu per %(brand)s.",
|
||||
"video_rooms_always_on_voip_channels": "Vaizdo kambariai - tai visada veikiantys VoIP kanalai, įterpti į %(brand)s kambarį.",
|
||||
@ -951,6 +1000,7 @@
|
||||
},
|
||||
"powered_by_matrix": "Veikia su Matrix",
|
||||
"presence": {
|
||||
"away": "Pasitraukę",
|
||||
"busy": "Užsiėmęs",
|
||||
"idle": "Neveiklus",
|
||||
"idle_for": "Neveiklus %(duration)s",
|
||||
@ -1414,8 +1464,10 @@
|
||||
"big_emoji": "Įjungti didelius jaustukus pokalbiuose",
|
||||
"code_block_expand_default": "Išplėsti kodo blokus pagal nutylėjimą",
|
||||
"code_block_line_numbers": "Rodyti eilučių numerius kodo blokuose",
|
||||
"disable_historical_profile": "Rodyti dabartinę profilio nuotrauką ir vardą naudotojams žinučių istorijoje",
|
||||
"emoji_autocomplete": "Įjungti Jaustukų pasiūlymus rašant",
|
||||
"enable_markdown": "Įjungti Markdown",
|
||||
"enable_markdown_description": "Pradėkite žinutes su <code>/plain</code> norint siųsti be ženklinimo.",
|
||||
"general": {
|
||||
"account_management_section": "Paskyros tvarkymas",
|
||||
"account_section": "Paskyra",
|
||||
@ -1466,7 +1518,7 @@
|
||||
"remove_email_prompt": "Pašalinti %(email)s?",
|
||||
"remove_msisdn_prompt": "Pašalinti %(phone)s?"
|
||||
},
|
||||
"inline_url_previews_default": "Įjungti URL nuorodų peržiūras kaip numatytasias",
|
||||
"inline_url_previews_default": "Įjungti peržiūras",
|
||||
"insert_trailing_colon_mentions": "Įterpti dvitaškį po naudotojo paminėjimų žinutės pradžioje",
|
||||
"jump_to_bottom_on_send": "Peršokti į laiko juostos apačią, kai siunčiate žinutę",
|
||||
"key_backup": {
|
||||
@ -1624,10 +1676,12 @@
|
||||
"verified_sessions_list_description": "Geriausiam saugumui, atsijunkite iš bet kurios sesijos, kurios neatpažįstate arba nebenaudojate.",
|
||||
"verify_session": "Patvirtinti seansą"
|
||||
},
|
||||
"show_avatar_changes": "Rodyti profilio nuotraukos pakeitimus",
|
||||
"show_breadcrumbs": "Rodyti neseniai peržiūrėtų kambarių nuorodas virš kambarių sąrašo",
|
||||
"show_chat_effects": "Rodyti pokalbių efektus (animaciją, kai gaunate, pvz., konfeti)",
|
||||
"show_displayname_changes": "Rodyti rodomo vardo pakeitimus",
|
||||
"show_join_leave": "Rodyti prisijungimo/išėjimo žinutes (kvietimai/pašalinimai/blokavimai neturi įtakos)",
|
||||
"show_nsfw_content": "Rodyti NSD turinį",
|
||||
"show_read_receipts": "Rodyti kitų vartotojų siųstus perskaitymo kvitus",
|
||||
"show_redaction_placeholder": "Rodyti pašalintų žinučių žymeklį",
|
||||
"show_stickers_button": "Rodyti lipdukų mygtuką",
|
||||
@ -1905,8 +1959,8 @@
|
||||
"m.room.member": {
|
||||
"accepted_3pid_invite": "%(targetName)s priėmė kvietimą, skirtą %(displayName)s",
|
||||
"accepted_invite": "%(targetName)s priėmė kvietimą",
|
||||
"ban": "%(senderName)s uždraudė %(targetName)s",
|
||||
"ban_reason": "%(senderName)s uždraudė %(targetName)s%(targetName)s: %(reason)s",
|
||||
"ban": "%(senderName)s uždraudė naudotoją",
|
||||
"ban_reason": "%(senderName)s uždraudė naudotoją: %(reason)s",
|
||||
"change_avatar": "%(senderName)s pakeitė savo profilio nuotrauką",
|
||||
"change_name": "%(oldDisplayName)s pasikeitė savo rodomą vardą į %(displayName)s",
|
||||
"change_name_avatar": "%(oldDisplayName)s pakeitė savo rodomą vardą ir profilio nuotrauką",
|
||||
@ -1994,7 +2048,8 @@
|
||||
"pending_moderation": "Žinutė laukia moderavimo",
|
||||
"pending_moderation_reason": "Žinutė laukia moderavimo: %(reason)s",
|
||||
"reactions": {
|
||||
"add_reaction_prompt": "Pridėti reakciją"
|
||||
"add_reaction_prompt": "Pridėti reakciją",
|
||||
"label": "%(reactors)s sureagavo su %(content)s"
|
||||
},
|
||||
"read_receipt_title": {
|
||||
"one": "Matė %(count)s žmogus",
|
||||
|
||||
@ -3354,7 +3354,6 @@
|
||||
"user_busy_description": "Lietotājs, kuram zvanāt, ir aizņemts.",
|
||||
"user_is_presenting": "%(sharerName)s prezentē",
|
||||
"video_call": "Video zvans",
|
||||
"video_call_started": "Videozvans uzsākts",
|
||||
"voice_call": "Balss zvans",
|
||||
"you_are_presenting": "Jūs prezentējat"
|
||||
},
|
||||
|
||||
@ -3394,7 +3394,6 @@
|
||||
"user_busy_description": "Mbola Sahirana ny mpampiasa niantsoanao.",
|
||||
"user_is_presenting": "%(sharerName)sIzao",
|
||||
"video_call": "Antso an-tsary",
|
||||
"video_call_started": "Manomboka ny antso an-tsary",
|
||||
"video_call_using": "Antso an-tsary mampiasa:",
|
||||
"voice_call": "Antso an-tariby",
|
||||
"you_are_presenting": "Mamkahalala anareo"
|
||||
|
||||
@ -588,7 +588,6 @@
|
||||
"video": "Video",
|
||||
"video_room": "Videorom",
|
||||
"view_message": "Se melding",
|
||||
"voice": "Tale",
|
||||
"warning": "Advarsel"
|
||||
},
|
||||
"composer": {
|
||||
@ -3859,7 +3858,6 @@
|
||||
"connection_lost": "Mistet forbindelsen til serveren",
|
||||
"connection_lost_description": "Du kan ikke ringe uten tilkobling til serveren.",
|
||||
"consulting": "Rådføring med %(transferTarget)s. <a>Overfør til %(transferee)s</a>",
|
||||
"decline_call": "Avslå",
|
||||
"default_device": "Standardenhet",
|
||||
"dial": "Ring",
|
||||
"dialpad": "Nummerpanel",
|
||||
@ -3910,7 +3908,6 @@
|
||||
"show_sidebar_button": "Vis sidepanel",
|
||||
"silence": "Demp samtale",
|
||||
"silenced": "Varslinger er dempet",
|
||||
"skip_lobby_toggle_option": "Bli med umiddelbart",
|
||||
"start_screenshare": "Begynn å dele skjermen din",
|
||||
"stop_screenshare": "Slutt å dele skjermen din",
|
||||
"too_many_calls": "For mange samtaler",
|
||||
@ -3932,7 +3929,6 @@
|
||||
"user_is_presenting": "%(sharerName)s presenterer",
|
||||
"video_call": "Videosamtale",
|
||||
"video_call_incoming": "Innkommende videosamtale",
|
||||
"video_call_started": "Videosamtale startet",
|
||||
"video_call_using": "Videosamtale ved hjelp av:",
|
||||
"voice_call": "Stemmesamtale",
|
||||
"voice_call_incoming": "Innkommende taleanrop",
|
||||
|
||||
@ -2960,7 +2960,6 @@
|
||||
"user_busy_description": "De persoon die je belde is bezet.",
|
||||
"user_is_presenting": "%(sharerName)s is aan het presenteren",
|
||||
"video_call": "Video-oproep",
|
||||
"video_call_started": "Videogesprek gestart",
|
||||
"voice_call": "Spraakoproep",
|
||||
"you_are_presenting": "Je bent aan het presenteren"
|
||||
},
|
||||
|
||||
@ -3829,7 +3829,6 @@
|
||||
"connection_lost": "Połączenie z serwerem zostało przerwane",
|
||||
"connection_lost_description": "Nie możesz wykonywać rozmów bez połączenia z serwerem.",
|
||||
"consulting": "Konsultowanie z %(transferTarget)s. <a>Transfer do %(transferee)s</a>",
|
||||
"decline_call": "Odrzuć",
|
||||
"default_device": "Urządzenie domyślne",
|
||||
"dial": "Wybierz numer",
|
||||
"dialpad": "Klawiatura telefoniczna",
|
||||
@ -3881,7 +3880,6 @@
|
||||
"show_sidebar_button": "Pokaż pasek boczny",
|
||||
"silence": "Wycisz rozmowę",
|
||||
"silenced": "Wyciszono powiadomienia",
|
||||
"skip_lobby_toggle_option": "Dołącz teraz",
|
||||
"start_screenshare": "Udostępnij ekran",
|
||||
"stop_screenshare": "Przestań udostępniać ekran",
|
||||
"too_many_calls": "Zbyt wiele połączeń",
|
||||
@ -3902,7 +3900,6 @@
|
||||
"user_busy_description": "Użytkownik, do którego zadzwoniłeś jest zajęty.",
|
||||
"user_is_presenting": "%(sharerName)s prezentuje",
|
||||
"video_call": "Rozmowa wideo",
|
||||
"video_call_started": "Rozpoczęto rozmowę wideo",
|
||||
"video_call_using": "Połączenie wideo przy użyciu:",
|
||||
"voice_call": "Rozmowa głosowa",
|
||||
"you_are_presenting": "Prezentujesz"
|
||||
|
||||
@ -3750,7 +3750,6 @@
|
||||
"user_busy_description": "O utilizador para o qual tentou ligar está ocupado.",
|
||||
"user_is_presenting": "%(sharerName)s está apresentando",
|
||||
"video_call": "Chamada de vídeo",
|
||||
"video_call_started": "Chamada de vídeo iniciada",
|
||||
"video_call_using": "Video-chamada usando:",
|
||||
"voice_call": "Chamada de voz",
|
||||
"you_are_presenting": "Estás a apresentar"
|
||||
|
||||
@ -587,7 +587,6 @@
|
||||
"video": "Vídeo",
|
||||
"video_room": "Sala de vídeo",
|
||||
"view_message": "Ver mensagem",
|
||||
"voice": "Voz",
|
||||
"warning": "Atenção"
|
||||
},
|
||||
"composer": {
|
||||
@ -3834,7 +3833,6 @@
|
||||
"connection_lost": "A conectividade com o servidor foi perdida",
|
||||
"connection_lost_description": "Você não pode fazer chamadas sem uma conexão com o servidor.",
|
||||
"consulting": "Consultar com %(transferTarget)s. Tranferir para <a> %(transferee)s</a>",
|
||||
"decline_call": "Recusar",
|
||||
"default_device": "Aparelho padrão",
|
||||
"dial": "Discar",
|
||||
"dialpad": "Teclado de discagem",
|
||||
@ -3885,7 +3883,6 @@
|
||||
"show_sidebar_button": "Exibir a barra lateral",
|
||||
"silence": "Silenciar chamado",
|
||||
"silenced": "Notificações silenciadas",
|
||||
"skip_lobby_toggle_option": "Junte-se imediatamente",
|
||||
"start_screenshare": "Começar a compartilhar sua tela",
|
||||
"stop_screenshare": "Parar de compartilhar sua tela",
|
||||
"too_many_calls": "Muitas chamadas",
|
||||
@ -3907,7 +3904,6 @@
|
||||
"user_is_presenting": "%(sharerName)s está apresentando",
|
||||
"video_call": "Chamada de vídeo",
|
||||
"video_call_incoming": "Chamada de vídeo recebida",
|
||||
"video_call_started": "Videochamada iniciada",
|
||||
"video_call_using": "Chamada de vídeo usando:",
|
||||
"voice_call": "Chamada de voz",
|
||||
"voice_call_incoming": "Chamada de voz recebida",
|
||||
|
||||
@ -589,7 +589,6 @@
|
||||
"video": "Видео",
|
||||
"video_room": "Видеочат",
|
||||
"view_message": "Посмотреть сообщение",
|
||||
"voice": "Голос",
|
||||
"warning": "Внимание"
|
||||
},
|
||||
"composer": {
|
||||
@ -681,6 +680,11 @@
|
||||
"unfederated_label_default_on": "Вы можете отключить это, если комната будет использоваться для совместной работы с внешними командами, у которых есть собственный домашний сервер. Это не может быть изменено позже.",
|
||||
"unsupported_version": "Сервер не поддерживает указанную версию чата."
|
||||
},
|
||||
"create_section_dialog": {
|
||||
"create_section": "Новый раздел",
|
||||
"label": "Название раздела",
|
||||
"title": "Новый раздел"
|
||||
},
|
||||
"create_space": {
|
||||
"add_details_prompt": "Добавьте некоторые подробности, чтобы помочь людям узнать его.",
|
||||
"add_details_prompt_2": "Вы можете изменить их в любое время.",
|
||||
@ -3900,7 +3904,6 @@
|
||||
"connection_lost": "Соединение с сервером потеряно",
|
||||
"connection_lost_description": "Вы не можете совершать вызовы без подключения к серверу.",
|
||||
"consulting": "Общение с %(transferTarget)s. <a>Перевод на %(transferee)s</a>",
|
||||
"decline_call": "Отклонить",
|
||||
"default_device": "Устройство по умолчанию",
|
||||
"dial": "Набор",
|
||||
"dialpad": "Панель набора номера",
|
||||
@ -3951,7 +3954,6 @@
|
||||
"show_sidebar_button": "Показать боковую панель",
|
||||
"silence": "Тихий вызов",
|
||||
"silenced": "Оповещения приглушены",
|
||||
"skip_lobby_toggle_option": "Присоединиться прямо сейчас",
|
||||
"start_screenshare": "Начать делиться экраном",
|
||||
"stop_screenshare": "Перестать делиться экраном",
|
||||
"too_many_calls": "Слишком много звонков",
|
||||
@ -3973,7 +3975,6 @@
|
||||
"user_is_presenting": "%(sharerName)s показывает",
|
||||
"video_call": "Видеовызов",
|
||||
"video_call_incoming": "Входящий видеозвонок",
|
||||
"video_call_started": "Начался видеозвонок",
|
||||
"video_call_using": "Видеозвонок с использованием:",
|
||||
"voice_call": "Голосовой вызов",
|
||||
"voice_call_incoming": "Входящий голосовой вызов",
|
||||
|
||||
@ -593,7 +593,6 @@
|
||||
"video": "Video",
|
||||
"video_room": "Video miestnosť",
|
||||
"view_message": "Zobraziť správu",
|
||||
"voice": "Hlas",
|
||||
"warning": "Upozornenie"
|
||||
},
|
||||
"composer": {
|
||||
@ -3938,7 +3937,6 @@
|
||||
"connection_lost": "Spojenie so serverom bolo prerušené",
|
||||
"connection_lost_description": "Bez pripojenia k serveru nie je možné uskutočňovať hovory.",
|
||||
"consulting": "Konzultovanie s %(transferTarget)s. <a>Presmerovanie na %(transferee)s</a>",
|
||||
"decline_call": "Zamietnuť",
|
||||
"default_device": "Predvolené zariadenie",
|
||||
"dial": "Vytočiť číslo",
|
||||
"dialpad": "Číselník",
|
||||
@ -3990,7 +3988,6 @@
|
||||
"show_sidebar_button": "Zobraziť bočný panel",
|
||||
"silence": "Stlmiť hovor",
|
||||
"silenced": "Oznámenia stlmené",
|
||||
"skip_lobby_toggle_option": "Pripojiť sa okamžite",
|
||||
"start_screenshare": "Spustiť zdieľanie vašej obrazovky",
|
||||
"stop_screenshare": "Zastaviť zdieľanie vašej obrazovky",
|
||||
"too_many_calls": "Príliš veľa hovorov",
|
||||
@ -4012,7 +4009,6 @@
|
||||
"user_is_presenting": "%(sharerName)s prezentuje",
|
||||
"video_call": "Video hovor",
|
||||
"video_call_incoming": "Prichádzajúci videohovor",
|
||||
"video_call_started": "Videohovor bol spustený",
|
||||
"video_call_using": "Videohovor pomocou:",
|
||||
"voice_call": "Hlasový hovor",
|
||||
"voice_call_incoming": "Prichádzajúci hlasový hovor",
|
||||
|
||||
@ -3196,7 +3196,6 @@
|
||||
"user_busy_description": "Përdoruesi që thirrët është i zënë.",
|
||||
"user_is_presenting": "%(sharerName)s përfaqëson",
|
||||
"video_call": "Thirrje video",
|
||||
"video_call_started": "Nisi thirrje me video",
|
||||
"voice_call": "Thirrje audio",
|
||||
"you_are_presenting": "Përfaqësoni"
|
||||
},
|
||||
|
||||
@ -3836,7 +3836,6 @@
|
||||
"user_busy_description": "Användaren du ringde är upptagen.",
|
||||
"user_is_presenting": "%(sharerName)s presenterar",
|
||||
"video_call": "Videosamtal",
|
||||
"video_call_started": "Videosamtal startat",
|
||||
"video_call_using": "Videosamtal med hjälp av:",
|
||||
"voice_call": "Röstsamtal",
|
||||
"you_are_presenting": "Du presenterar"
|
||||
|
||||
@ -3736,7 +3736,6 @@
|
||||
"user_busy_description": "Aradığınız kullanıcı meşgul.",
|
||||
"user_is_presenting": "%(sharerName)s sunum yapıyor",
|
||||
"video_call": "Görüntülü arama",
|
||||
"video_call_started": "Görüntülü arama başlatıldı",
|
||||
"video_call_using": "Görüntülü arama kullanılıyor:",
|
||||
"voice_call": "Sesli arama",
|
||||
"you_are_presenting": "Sunum yapıyorsunuz"
|
||||
|
||||
@ -589,7 +589,6 @@
|
||||
"video": "Відео",
|
||||
"video_room": "Відеокімната",
|
||||
"view_message": "Переглянути повідомлення",
|
||||
"voice": "Голос",
|
||||
"warning": "Попередження"
|
||||
},
|
||||
"composer": {
|
||||
@ -3870,7 +3869,6 @@
|
||||
"connection_lost": "Втрачено зʼєднання з сервером",
|
||||
"connection_lost_description": "Неможливо здійснювати виклики без з'єднання з сервером.",
|
||||
"consulting": "Консультація з %(transferTarget)s. <a>Переадресація на %(transferee)s</a>",
|
||||
"decline_call": "Відхилити",
|
||||
"default_device": "Уставний пристрій",
|
||||
"dial": "Виклик",
|
||||
"dialpad": "Номеронабирач",
|
||||
@ -3922,7 +3920,6 @@
|
||||
"show_sidebar_button": "Показати бічну панель",
|
||||
"silence": "Тихий виклик",
|
||||
"silenced": "Сповіщення стишено",
|
||||
"skip_lobby_toggle_option": "Приєднатися негайно",
|
||||
"start_screenshare": "Почати показ екрана",
|
||||
"stop_screenshare": "Вимкнути показ екрана",
|
||||
"too_many_calls": "Забагато викликів",
|
||||
@ -3944,7 +3941,6 @@
|
||||
"user_is_presenting": "%(sharerName)s показує",
|
||||
"video_call": "Відеовиклик",
|
||||
"video_call_incoming": "Вхідний відеовиклик",
|
||||
"video_call_started": "Відеовиклик розпочато",
|
||||
"video_call_using": "Відеодзвінок за допомогою:",
|
||||
"voice_call": "Голосовий виклик",
|
||||
"voice_call_incoming": "Вхідний голосовий виклик",
|
||||
|
||||
@ -3137,7 +3137,6 @@
|
||||
"user_busy_description": "Người dùng bạn vừa gọi hiện đang bận.",
|
||||
"user_is_presenting": "%(sharerName)s đang trình bày",
|
||||
"video_call": "Gọi video",
|
||||
"video_call_started": "Cuộc gọi truyền hình đã bắt đầu",
|
||||
"voice_call": "Gọi thoại",
|
||||
"you_are_presenting": "Bạn đang trình bày"
|
||||
},
|
||||
|
||||
@ -219,7 +219,7 @@
|
||||
"incorrect_password": "密码不正确",
|
||||
"log_in_new_account": "<a>登录</a>到你的新账户。",
|
||||
"logout_dialog": {
|
||||
"description": "你确定要注销吗?",
|
||||
"description": "你确定要移除此设备?",
|
||||
"megolm_export": "手动导出密钥",
|
||||
"setup_key_backup_title": "你将失去加密消息的访问权",
|
||||
"setup_secure_backup_description_1": "加密消息采用端到端加密技术确保安全。只有你与收件人拥有读取这些消息的密钥。",
|
||||
@ -588,7 +588,6 @@
|
||||
"video": "视频",
|
||||
"video_room": "视频房间",
|
||||
"view_message": "查看消息",
|
||||
"voice": "语音",
|
||||
"warning": "警告"
|
||||
},
|
||||
"composer": {
|
||||
@ -684,8 +683,10 @@
|
||||
"create_section_dialog": {
|
||||
"create_section": "创建区域",
|
||||
"description": "区域仅对你可见",
|
||||
"edit_section": "编辑区域",
|
||||
"label": "区域名称",
|
||||
"title": "创建区域"
|
||||
"title": "创建区域",
|
||||
"title_edition": "编辑区域"
|
||||
},
|
||||
"create_space": {
|
||||
"add_details_prompt": "添加一些信息以便人们识别。",
|
||||
@ -1116,7 +1117,7 @@
|
||||
"waiting_other_device_details": "等待你在另一台设备上验证,%(deviceName)s(%(deviceId)s)…",
|
||||
"waiting_other_user": "正在等待 %(displayName)s 验证…"
|
||||
},
|
||||
"verification_requested_toast_title": "已请求验证",
|
||||
"verification_requested_toast_title": "请求验证",
|
||||
"verified_identity_changed": "%(displayName)s (<b>%(userId)s</b>) 的数字身份已重置。<a>了解更多</a>",
|
||||
"verified_identity_changed_no_displayname": "<b>%(userId)s</b>的数字身份已重置。<a>了解更多</a>",
|
||||
"verify_toast_description": "可能不受其他用户信任",
|
||||
@ -1433,7 +1434,7 @@
|
||||
},
|
||||
"keyboard": {
|
||||
"activate_button": "激活选择的按钮",
|
||||
"alt": "Alt",
|
||||
"alt": "",
|
||||
"autocomplete_cancel": "取消自动补全",
|
||||
"autocomplete_force": "强制完成",
|
||||
"autocomplete_navigate_next": "下一个自动补全建议",
|
||||
@ -1457,11 +1458,11 @@
|
||||
"composer_toggle_link": "切换链接",
|
||||
"composer_toggle_quote": "切换引用",
|
||||
"composer_undo": "撤消编辑",
|
||||
"control": "Ctrl",
|
||||
"control": "",
|
||||
"dismiss_read_marker_and_jump_bottom": "忽略已读标记并跳转到底部",
|
||||
"end": "结束",
|
||||
"end": "",
|
||||
"enter": "",
|
||||
"escape": "Esc",
|
||||
"escape": "",
|
||||
"go_home_view": "转到主页视图",
|
||||
"home": "",
|
||||
"jump_first_message": "跳转到首个消息",
|
||||
@ -1493,7 +1494,7 @@
|
||||
"scroll_up_timeline": "向上滚动时间线",
|
||||
"search": "搜索(要使其生效必须启用相关功能)",
|
||||
"send_sticker": "发送贴纸",
|
||||
"shift": "Shift",
|
||||
"shift": "",
|
||||
"space": "空格",
|
||||
"switch_to_space": "使用数字切换空间",
|
||||
"toggle_hidden_events": "切换隐藏事件的可见性",
|
||||
@ -1843,6 +1844,12 @@
|
||||
"ongoing": "正在移除…",
|
||||
"reason_label": "理由(可选)"
|
||||
},
|
||||
"remove_section_dialog": {
|
||||
"confirmation": "你确定要移除此区域?",
|
||||
"description": "此区域中的聊天仍将显示在聊天列表。",
|
||||
"remove_section": "移除区域",
|
||||
"title": "移除此区域?"
|
||||
},
|
||||
"report_content": {
|
||||
"description": "举报此消息会将其唯一的“事件 ID”发送给服务器管理员。如果此房间中的消息已加密,则服务器管理员将无法查看消息文本、任何文件或图像。",
|
||||
"disagree": "不同意",
|
||||
@ -2452,7 +2459,7 @@
|
||||
"description_3": "浏览器扩展阻止了该请求。",
|
||||
"description_4": "此服务器已离线。",
|
||||
"description_5": "服务器已拒绝你的请求。",
|
||||
"description_6": "你所在的区域在连接 Internet 时遇到困难。",
|
||||
"description_6": "你所在的地区在连接 Internet 时遇到困难。",
|
||||
"description_7": "尝试联系服务器时发生连接错误。",
|
||||
"description_8": "服务器的配置未能说明问题原因(CORS)。",
|
||||
"empty_timeline": "你已阅读所有消息",
|
||||
@ -2573,7 +2580,7 @@
|
||||
"dialog_title": "<strong>设置:</strong>加密",
|
||||
"key_storage": {
|
||||
"allow_key_storage": "允许密钥存储",
|
||||
"description": "这将允许你在任意新设备上查看聊天历史,这对备份聊天和数字身份是必需的。<a了解更多</a>",
|
||||
"description": "这将允许你在任意新设备上查看聊天历史,这对备份聊天和数字身份是必需的。<a>了解更多</a>",
|
||||
"title": "密钥存储"
|
||||
},
|
||||
"recovery": {
|
||||
@ -3741,7 +3748,7 @@
|
||||
"other": "%(names)s 与其他 %(count)s 个人正在输入…"
|
||||
},
|
||||
"one_user": "%(displayName)s 正在输入…",
|
||||
"two_users": "%(names)s 与其他 %(lastPerson)s 个人正在输入…"
|
||||
"two_users": "%(names)s 与 %(lastPerson)s 正在输入…"
|
||||
},
|
||||
"undecryptable_tooltip": "此消息无法解密"
|
||||
},
|
||||
@ -3894,7 +3901,6 @@
|
||||
"connection_lost": "与服务器的连接已丢失",
|
||||
"connection_lost_description": "在未连接到服务器的情况下,你无法拨打电话。",
|
||||
"consulting": "正在与 %(transferTarget)s 协商。<a>转接到 %(transferee)s</a>",
|
||||
"decline_call": "拒绝",
|
||||
"default_device": "默认设备",
|
||||
"dial": "拨号",
|
||||
"dialpad": "拨号盘",
|
||||
@ -3945,7 +3951,6 @@
|
||||
"show_sidebar_button": "显示边栏",
|
||||
"silence": "静音通话",
|
||||
"silenced": "通知已静音",
|
||||
"skip_lobby_toggle_option": "立即加入",
|
||||
"start_screenshare": "开始分享屏幕",
|
||||
"stop_screenshare": "停止分享屏幕",
|
||||
"too_many_calls": "呼叫频繁",
|
||||
@ -3967,7 +3972,6 @@
|
||||
"user_is_presenting": "%(sharerName)s 正在分享",
|
||||
"video_call": "视频通话",
|
||||
"video_call_incoming": "视频通话来电",
|
||||
"video_call_started": "已开始视频通话",
|
||||
"video_call_using": "视频通话时使用:",
|
||||
"voice_call": "语音通话",
|
||||
"voice_call_incoming": "语音通话来电",
|
||||
|
||||
@ -3399,7 +3399,6 @@
|
||||
"user_busy_description": "您想要通話的使用者目前忙碌中。",
|
||||
"user_is_presenting": "%(sharerName)s 正在投影",
|
||||
"video_call": "視訊通話",
|
||||
"video_call_started": "視訊通話已開始",
|
||||
"voice_call": "語音通話",
|
||||
"you_are_presenting": "您正在投影"
|
||||
},
|
||||
|
||||
@ -11,7 +11,7 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import { ALL_RULE_TYPES, BanList } from "./BanList";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import SettingsStore, { type CallbackFn } from "../settings/SettingsStore";
|
||||
import { _t } from "../languageHandler";
|
||||
import dis from "../dispatcher/dispatcher";
|
||||
import { SettingLevel } from "../settings/SettingLevel";
|
||||
@ -38,7 +38,7 @@ export class Mjolnir {
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
this.mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this.onListsChanged.bind(this));
|
||||
this.mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this.onListsChanged);
|
||||
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
dis.dispatch<DoAfterSyncPreparedPayload<ActionPayload>>({
|
||||
@ -130,15 +130,10 @@ export class Mjolnir {
|
||||
this.updateLists(this._roomIds);
|
||||
};
|
||||
|
||||
private onListsChanged(
|
||||
settingName: string,
|
||||
roomId: string | null,
|
||||
atLevel: SettingLevel,
|
||||
newValue: string[],
|
||||
): void {
|
||||
private onListsChanged: CallbackFn<"mjolnirRooms"> = (settingName, roomId, atLevel, newValue): void => {
|
||||
// We know that ban lists are only recorded at one level so we don't need to re-eval them
|
||||
this.updateLists(newValue);
|
||||
}
|
||||
this.updateLists(newValue ?? []);
|
||||
};
|
||||
|
||||
private updateLists(listRoomIds: string[]): void {
|
||||
if (!MatrixClientPeg.get()) return;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
Copyright 2024, 2025 New Vector Ltd.
|
||||
Copyright 2018-2024 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2017 Travis Ralston
|
||||
@ -53,6 +54,7 @@ import { type ComputedInviteConfig } from "../@types/invite-rules.ts";
|
||||
import BlockInvitesConfigController from "./controllers/BlockInvitesConfigController.ts";
|
||||
import RequiresSettingsController from "./controllers/RequiresSettingsController.ts";
|
||||
import { type OrderedCustomSections, type CustomSectionsData } from "../stores/room-list-v3/section.ts";
|
||||
import { type NotificationSound } from "../Notifier.ts";
|
||||
|
||||
export const defaultWatchManager = new WatchManager();
|
||||
|
||||
@ -228,6 +230,7 @@ export interface Settings {
|
||||
"feature_ask_to_join": IFeature;
|
||||
"feature_notifications": IFeature;
|
||||
"feature_msc4362_encrypted_state_events": IFeature;
|
||||
"feature_user_status": IFeature;
|
||||
// These are in the feature namespace but aren't actually features
|
||||
"feature_hidebold": IBaseSetting<boolean>;
|
||||
|
||||
@ -309,15 +312,7 @@ export interface Settings {
|
||||
"urlPreviewsEnabled_e2ee": IBaseSetting<boolean>;
|
||||
"notificationsEnabled": IBaseSetting<boolean>;
|
||||
"deviceNotificationsEnabled": IBaseSetting<boolean>;
|
||||
"notificationSound": IBaseSetting<
|
||||
| {
|
||||
name: string;
|
||||
type: string;
|
||||
size: number;
|
||||
url: string;
|
||||
}
|
||||
| false
|
||||
>;
|
||||
"notificationSound": IBaseSetting<NotificationSound | false>;
|
||||
"notificationBodyEnabled": IBaseSetting<boolean>;
|
||||
"audioNotificationsEnabled": IBaseSetting<boolean>;
|
||||
"enableWidgetScreenshots": IBaseSetting<boolean>;
|
||||
@ -789,6 +784,30 @@ export const SETTINGS: Settings = {
|
||||
shouldWarn: true,
|
||||
default: false,
|
||||
},
|
||||
"feature_user_status": {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Profile,
|
||||
displayName: _td("labs|feature_user_status|display_name"),
|
||||
description: _td("labs|feature_user_status|description"),
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
|
||||
supportedLevelsAreOrdered: true,
|
||||
controller: new ServerSupportUnstableFeatureController(
|
||||
"feature_user_status",
|
||||
defaultWatchManager,
|
||||
[["org.matrix.msc4429"], ["org.matrix.msc4429.stable"]],
|
||||
undefined,
|
||||
_td("labs|feature_user_status|required_msc_support"),
|
||||
false,
|
||||
// We have to assume it's available during early startup because of a race:
|
||||
// The feature is used to enable extra sync filters during MatrixClient setup
|
||||
// and we can't check for serverside support until the client has finished setting up.
|
||||
// Once the client has setup, (so by the time the user actually opens the labs menu) we can
|
||||
// enforce proper checks.
|
||||
true,
|
||||
true,
|
||||
),
|
||||
default: false,
|
||||
},
|
||||
"useCompactLayout": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
displayName: _td("settings|preferences|compact_modern"),
|
||||
@ -1126,7 +1145,13 @@ export const SETTINGS: Settings = {
|
||||
supportedLevelsAreOrdered: true,
|
||||
displayName: _td("settings|inline_url_previews_default"),
|
||||
default: true,
|
||||
controller: new UIFeatureController(UIFeature.URLPreviews),
|
||||
controller: new RequiresSettingsController([UIFeature.URLPreviews], false, (c) => {
|
||||
if (c["io.element.msc4452.preview_url"]?.enabled !== false) {
|
||||
// If the capability is not listed, or explicitly true then do not disable.
|
||||
return false;
|
||||
}
|
||||
return _t("common|disabled_by_homeserver");
|
||||
}),
|
||||
},
|
||||
"urlPreviewsEnabled_e2ee": {
|
||||
// Can only be enabled per-device to ensure neither the homeserver nor client config
|
||||
|
||||
@ -79,7 +79,7 @@ export const LEVEL_ORDER = [
|
||||
SettingLevel.DEFAULT,
|
||||
];
|
||||
|
||||
function getLevelOrder(setting: ISetting): SettingLevel[] {
|
||||
function getLevelOrder(setting: Settings[keyof Settings]): SettingLevel[] {
|
||||
// Settings which support only a single setting level are inherently ordered
|
||||
if (setting.supportedLevelsAreOrdered || setting.supportedLevels.length === 1) {
|
||||
// return a copy to prevent callers from modifying the array
|
||||
@ -88,12 +88,12 @@ function getLevelOrder(setting: ISetting): SettingLevel[] {
|
||||
return LEVEL_ORDER;
|
||||
}
|
||||
|
||||
export type CallbackFn = (
|
||||
settingName: SettingKey,
|
||||
export type CallbackFn<S extends SettingKey> = (
|
||||
settingName: S,
|
||||
roomId: string | null,
|
||||
atLevel: SettingLevel,
|
||||
newValAtLevel: any,
|
||||
newVal: any,
|
||||
newValAtLevel: Settings[S]["default"] | null,
|
||||
newVal: Settings[S]["default"] | null,
|
||||
) => void;
|
||||
|
||||
type HandlerMap = Partial<{
|
||||
@ -167,7 +167,11 @@ export default class SettingsStore {
|
||||
* if the change in value is worthwhile enough to react upon.
|
||||
* @returns {string} A reference to the watcher that was employed.
|
||||
*/
|
||||
public static watchSetting(settingName: SettingKey, roomId: string | null, callbackFn: CallbackFn): string {
|
||||
public static watchSetting<S extends SettingKey>(
|
||||
settingName: S,
|
||||
roomId: string | null,
|
||||
callbackFn: CallbackFn<S>,
|
||||
): string {
|
||||
const setting = SETTINGS[settingName];
|
||||
if (!setting) throw new Error(`${settingName} is not a setting`);
|
||||
|
||||
@ -175,7 +179,11 @@ export default class SettingsStore {
|
||||
|
||||
const watcherId = `${new Date().getTime()}_${SettingsStore.watcherCount++}_${finalSettingName}_${roomId}`;
|
||||
|
||||
const localizedCallback = (changedInRoomId: string | null, atLevel: SettingLevel, newValAtLevel: any): void => {
|
||||
const localizedCallback = (
|
||||
changedInRoomId: string | null,
|
||||
atLevel: SettingLevel,
|
||||
newValAtLevel: Settings[S]["default"],
|
||||
): void => {
|
||||
if (!SettingsStore.doesSettingSupportLevel(settingName, atLevel)) {
|
||||
logger.warn(
|
||||
`Setting handler notified for an update of an invalid setting level: ` +
|
||||
@ -220,7 +228,7 @@ export default class SettingsStore {
|
||||
* @param {string} settingName The setting name to monitor.
|
||||
* @param {String} roomId The room ID to monitor for changes in. Use null for all rooms.
|
||||
*/
|
||||
public static monitorSetting(settingName: SettingKey, roomId: string | null): void {
|
||||
public static monitorSetting<S extends SettingKey>(settingName: S, roomId: string | null): void {
|
||||
roomId = roomId || null; // the thing wants null specifically to work, so appease it.
|
||||
|
||||
if (!this.monitors.has(settingName)) this.monitors.set(settingName, new Map());
|
||||
@ -228,7 +236,7 @@ export default class SettingsStore {
|
||||
const registerWatcher = (): void => {
|
||||
this.monitors.get(settingName)!.set(
|
||||
roomId,
|
||||
SettingsStore.watchSetting(
|
||||
SettingsStore.watchSetting<S>(
|
||||
settingName,
|
||||
roomId,
|
||||
(settingName, inRoomId, level, newValueAtLevel, newValue) => {
|
||||
@ -449,11 +457,10 @@ export default class SettingsStore {
|
||||
|
||||
/**
|
||||
* Gets the default value of a setting.
|
||||
* @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.
|
||||
* @return {*} The default value
|
||||
* @param settingName The name of the setting to read the value of.
|
||||
* @return The default value
|
||||
*/
|
||||
public static getDefaultValue(settingName: SettingKey): any {
|
||||
public static getDefaultValue<S extends SettingKey>(settingName: S): Settings[S]["default"] {
|
||||
// Verify that the setting is actually a setting
|
||||
if (!SETTINGS[settingName]) {
|
||||
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
|
||||
@ -462,13 +469,13 @@ export default class SettingsStore {
|
||||
return SETTINGS[settingName].default;
|
||||
}
|
||||
|
||||
private static getFinalValue(
|
||||
setting: ISetting,
|
||||
private static getFinalValue<S extends SettingKey>(
|
||||
setting: Settings[S],
|
||||
level: SettingLevel,
|
||||
roomId: string | null,
|
||||
calculatedValue: any,
|
||||
calculatedValue: Settings[S]["default"],
|
||||
calculatedAtLevel: SettingLevel | null,
|
||||
): any {
|
||||
): Settings[S]["default"] {
|
||||
let resultingValue = calculatedValue;
|
||||
|
||||
if (setting.controller) {
|
||||
@ -480,25 +487,22 @@ export default class SettingsStore {
|
||||
return resultingValue;
|
||||
}
|
||||
|
||||
/* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307
|
||||
/**
|
||||
* Sets the value for a setting. The room ID is optional if the setting is not being
|
||||
* set for a particular room, otherwise it should be supplied. The value may be null
|
||||
* to indicate that the level should no longer have an override.
|
||||
* @param {string} settingName The name of the setting to change.
|
||||
* @param {String} roomId The room ID to change the value in, may be null.
|
||||
* @param {SettingLevel} level The level
|
||||
* @param settingName The name of the setting to change.
|
||||
* @param roomId The room ID to change the value in, may be null.
|
||||
* @param 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.
|
||||
* @param value The new value of the setting, may be null.
|
||||
* @return Resolves when the setting has been changed.
|
||||
*/
|
||||
|
||||
/* eslint-enable valid-jsdoc */
|
||||
public static async setValue(
|
||||
settingName: SettingKey,
|
||||
public static async setValue<S extends SettingKey>(
|
||||
settingName: S,
|
||||
roomId: string | null,
|
||||
level: SettingLevel,
|
||||
value: any,
|
||||
value: Settings[S]["default"] | null,
|
||||
): Promise<void> {
|
||||
// Verify that the setting is actually a setting
|
||||
const setting = SETTINGS[settingName];
|
||||
|
||||
@ -38,8 +38,8 @@ export default class MediaPreviewConfigController extends MatrixClientBackedCont
|
||||
const validMediaPreviews = Object.values(MediaPreviewValue);
|
||||
const validInviteAvatars = [MediaPreviewValue.Off, MediaPreviewValue.On];
|
||||
return {
|
||||
invite_avatars: validInviteAvatars.includes(inviteAvatars) ? inviteAvatars : undefined,
|
||||
media_previews: validMediaPreviews.includes(mediaPreviews) ? mediaPreviews : undefined,
|
||||
invite_avatars: validInviteAvatars.includes(inviteAvatars!) ? inviteAvatars : undefined,
|
||||
media_previews: validMediaPreviews.includes(mediaPreviews!) ? mediaPreviews : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user