Compare commits
16 Commits
t3chguy/re
...
hs/fosdem2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d733f65cf3 | ||
|
|
07f1680ba0 | ||
|
|
3fbc9e6de6 | ||
|
|
e7d9df24e2 | ||
|
|
ad77f7943b | ||
|
|
89d7dca464 | ||
|
|
af3040fb62 | ||
|
|
b6ba3335ec | ||
|
|
6b7c94905f | ||
|
|
a4e8bb3f9a | ||
|
|
2b4000d47f | ||
|
|
01304439ee | ||
|
|
c659afa8db | ||
|
|
9cc5564d50 | ||
|
|
549300726f | ||
|
|
319dab5920 |
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -13,6 +13,7 @@
|
||||
|
||||
# Ignore translations as those will be updated by GHA for Localazy download
|
||||
/src/i18n/strings
|
||||
/src/i18n/strings/en_EN.json @element-hq/element-web-reviewers
|
||||
# Ignore the synapse plugin as this is updated by GHA for docker image updating
|
||||
/playwright/plugins/homeserver/synapse/index.ts
|
||||
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,3 +1,38 @@
|
||||
Changes in [1.11.89](https://github.com/element-hq/element-web/releases/tag/v1.11.89) (2024-12-18)
|
||||
==================================================================================================
|
||||
This is a patch release to fix a bug which could prevent loading stored crypto state from storage, and also to fix URL previews when switching back to a room.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Upgrade matrix-sdk-crypto-wasm to 1.11.0 (https://github.com/matrix-org/matrix-js-sdk/pull/4593)
|
||||
* Fix url preview display ([#28766](https://github.com/element-hq/element-web/pull/28766)).
|
||||
|
||||
|
||||
Changes in [1.11.88](https://github.com/element-hq/element-web/releases/tag/v1.11.88) (2024-12-17)
|
||||
==================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Allow trusted Element Call widget to send and receive media encryption key to-device messages ([#28316](https://github.com/element-hq/element-web/pull/28316)). Contributed by @hughns.
|
||||
* increase ringing timeout from 10 seconds to 90 seconds ([#28630](https://github.com/element-hq/element-web/pull/28630)). Contributed by @fkwp.
|
||||
* Add `Close` tooltip to dialog ([#28617](https://github.com/element-hq/element-web/pull/28617)). Contributed by @florianduros.
|
||||
* New UX for Share dialog ([#28598](https://github.com/element-hq/element-web/pull/28598)). Contributed by @florianduros.
|
||||
* Improve performance of RoomContext in RoomHeader ([#28574](https://github.com/element-hq/element-web/pull/28574)). Contributed by @t3chguy.
|
||||
* Remove `Features.RustCrypto` flag ([#28582](https://github.com/element-hq/element-web/pull/28582)). Contributed by @florianduros.
|
||||
* Add Modernizr warning when running in non-secure context ([#28581](https://github.com/element-hq/element-web/pull/28581)). Contributed by @t3chguy.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix jumpy timeline when the pinned message banner is displayed ([#28654](https://github.com/element-hq/element-web/pull/28654)). Contributed by @florianduros.
|
||||
* Fix font \& spaces in settings subsection ([#28631](https://github.com/element-hq/element-web/pull/28631)). Contributed by @florianduros.
|
||||
* Remove manual device verification which is not supported by the new cryptography stack ([#28588](https://github.com/element-hq/element-web/pull/28588)). Contributed by @florianduros.
|
||||
* Fix code block highlighting not working reliably with many code blocks ([#28613](https://github.com/element-hq/element-web/pull/28613)). Contributed by @t3chguy.
|
||||
* Remove remaining reply fallbacks code ([#28610](https://github.com/element-hq/element-web/pull/28610)). Contributed by @t3chguy.
|
||||
* Provide a way to activate GIFs via the keyboard for a11y ([#28611](https://github.com/element-hq/element-web/pull/28611)). Contributed by @t3chguy.
|
||||
* Fix format bar position ([#28591](https://github.com/element-hq/element-web/pull/28591)). Contributed by @florianduros.
|
||||
* Fix room taking long time to load ([#28579](https://github.com/element-hq/element-web/pull/28579)). Contributed by @florianduros.
|
||||
* Show the correct shield status in tooltip for more conditions ([#28476](https://github.com/element-hq/element-web/pull/28476)). Contributed by @uhoreg.
|
||||
|
||||
|
||||
Changes in [1.11.87](https://github.com/element-hq/element-web/releases/tag/v1.11.87) (2024-12-03)
|
||||
==================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "element-web",
|
||||
"version": "1.11.87",
|
||||
"version": "1.11.89",
|
||||
"description": "A feature-rich client for Matrix.org",
|
||||
"author": "New Vector Ltd.",
|
||||
"repository": {
|
||||
@@ -124,7 +124,7 @@
|
||||
"maplibre-gl": "^4.0.0",
|
||||
"matrix-encrypt-attachment": "^1.0.3",
|
||||
"matrix-events-sdk": "0.0.1",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||
"matrix-js-sdk": "35.1.0",
|
||||
"matrix-widget-api": "^1.10.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
"mime": "^4.0.4",
|
||||
|
||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { type Preset, type Visibility } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import type { Page } from "@playwright/test";
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { doTwoWaySasVerification, awaitVerifier } from "./utils";
|
||||
import { Client } from "../../pages/client";
|
||||
@@ -38,6 +39,8 @@ test.describe("User verification", () => {
|
||||
toasts,
|
||||
room: { roomId: dmRoomId },
|
||||
}) => {
|
||||
await waitForDeviceKeys(page);
|
||||
|
||||
// once Alice has joined, Bob starts the verification
|
||||
const bobVerificationRequest = await bob.evaluateHandle(
|
||||
async (client, { dmRoomId, aliceCredentials }) => {
|
||||
@@ -87,6 +90,8 @@ test.describe("User verification", () => {
|
||||
toasts,
|
||||
room: { roomId: dmRoomId },
|
||||
}) => {
|
||||
await waitForDeviceKeys(page);
|
||||
|
||||
// once Alice has joined, Bob starts the verification
|
||||
const bobVerificationRequest = await bob.evaluateHandle(
|
||||
async (client, { dmRoomId, aliceCredentials }) => {
|
||||
@@ -149,3 +154,15 @@ async function createDMRoom(client: Client, userId: string): Promise<string> {
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until we get the other user's device keys.
|
||||
* In newer rust-crypto versions, the verification request will be ignored if we
|
||||
* don't have the sender's device keys.
|
||||
*/
|
||||
async function waitForDeviceKeys(page: Page): Promise<void> {
|
||||
await expect(page.getByRole("button", { name: "Avatar" })).toBeVisible();
|
||||
const avatar = await page.getByRole("button", { name: "Avatar" });
|
||||
await avatar.click();
|
||||
await expect(page.getByText("1 session")).toBeVisible();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand";
|
||||
// Docker tag to use for synapse docker image.
|
||||
// We target a specific digest as every now and then a Synapse update will break our CI.
|
||||
// This digest is updated by the playwright-image-updates.yaml workflow periodically.
|
||||
const DOCKER_TAG = "develop@sha256:48308e18c5b3ad20bc0d090119618f45b6be4ba727522e37fbf7827d1a109531";
|
||||
const DOCKER_TAG = "develop@sha256:6b82dba715fa7ae641010b4cc5e71edaeb9cc05a50ac5b9e4ff09afa9cd2a80d";
|
||||
|
||||
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
|
||||
const templateDir = path.join(__dirname, "templates", opts.template);
|
||||
|
||||
@@ -13,6 +13,7 @@ import DMRoomMap from "./utils/DMRoomMap";
|
||||
import { mediaFromMxc } from "./customisations/Media";
|
||||
import { isLocalRoom } from "./utils/localRoom/isLocalRoom";
|
||||
import { getFirstGrapheme } from "./utils/strings";
|
||||
import { ModuleRunner } from "./modules/ModuleRunner";
|
||||
|
||||
/**
|
||||
* Hardcoded from the Compound colors.
|
||||
@@ -98,6 +99,12 @@ const colorToDataURLCache = new Map<string, string>();
|
||||
|
||||
export function defaultAvatarUrlForString(s: string): string {
|
||||
if (!s) return ""; // XXX: should never happen but empirically does by evidence of a rageshake
|
||||
|
||||
const avatar = ModuleRunner.instance.extensions.conference.defaultAvatarUrlForString(s);
|
||||
if (avatar !== null) {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const colorIndex = useIdColorHash(s);
|
||||
// overwritten color value in custom themes
|
||||
@@ -134,6 +141,11 @@ export function getInitialLetter(name: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const avatar = ModuleRunner.instance.extensions.conference.getAvatarInitialLetter(name);
|
||||
if (avatar !== null) {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
const initial = name[0];
|
||||
if ((initial === "@" || initial === "#" || initial === "+") && name[1]) {
|
||||
name = name.substring(1);
|
||||
|
||||
@@ -8,25 +8,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps, forwardRef, Ref } from "react";
|
||||
import React, { forwardRef, Ref } from "react";
|
||||
|
||||
import AccessibleButton from "../../components/views/elements/AccessibleButton";
|
||||
import AccessibleButton, { ButtonProps } from "../../components/views/elements/AccessibleButton";
|
||||
|
||||
type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleButton<T>> & {
|
||||
type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
|
||||
label?: string;
|
||||
// whether the context menu is currently open
|
||||
isExpanded: boolean;
|
||||
};
|
||||
|
||||
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
|
||||
export const ContextMenuButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
|
||||
{ label, isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>,
|
||||
ref: Ref<HTMLElement>,
|
||||
export const ContextMenuButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>(
|
||||
{ label, isExpanded, children, onClick, onContextMenu, ...props }: Props<T>,
|
||||
ref: Ref<HTMLElementTagNameMap[T]>,
|
||||
) {
|
||||
return (
|
||||
<AccessibleButton
|
||||
{...props}
|
||||
element={element as keyof JSX.IntrinsicElements}
|
||||
onClick={onClick}
|
||||
onContextMenu={onContextMenu ?? onClick ?? undefined}
|
||||
aria-label={label}
|
||||
|
||||
@@ -8,24 +8,23 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps, forwardRef, Ref } from "react";
|
||||
import React, { forwardRef, Ref } from "react";
|
||||
|
||||
import AccessibleButton from "../../components/views/elements/AccessibleButton";
|
||||
import AccessibleButton, { ButtonProps } from "../../components/views/elements/AccessibleButton";
|
||||
|
||||
type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleButton<T>> & {
|
||||
type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
|
||||
// whether the context menu is currently open
|
||||
isExpanded: boolean;
|
||||
};
|
||||
|
||||
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
|
||||
export const ContextMenuTooltipButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
|
||||
{ isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>,
|
||||
ref: Ref<HTMLElement>,
|
||||
export const ContextMenuTooltipButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>(
|
||||
{ isExpanded, children, onClick, onContextMenu, ...props }: Props<T>,
|
||||
ref: Ref<HTMLElementTagNameMap[T]>,
|
||||
) {
|
||||
return (
|
||||
<AccessibleButton
|
||||
{...props}
|
||||
element={element as keyof JSX.IntrinsicElements}
|
||||
onClick={onClick}
|
||||
onContextMenu={onContextMenu ?? onClick ?? undefined}
|
||||
aria-haspopup={true}
|
||||
|
||||
@@ -6,39 +6,33 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps } from "react";
|
||||
import React, { RefObject } from "react";
|
||||
|
||||
import AccessibleButton from "../../components/views/elements/AccessibleButton";
|
||||
import AccessibleButton, { ButtonProps } from "../../components/views/elements/AccessibleButton";
|
||||
import { useRovingTabIndex } from "../RovingTabIndex";
|
||||
import { Ref } from "./types";
|
||||
|
||||
type Props<T extends keyof JSX.IntrinsicElements> = Omit<
|
||||
ComponentProps<typeof AccessibleButton<T>>,
|
||||
"inputRef" | "tabIndex"
|
||||
> & {
|
||||
inputRef?: Ref;
|
||||
type Props<T extends keyof HTMLElementTagNameMap> = Omit<ButtonProps<T>, "tabIndex"> & {
|
||||
inputRef?: RefObject<HTMLElementTagNameMap[T]>;
|
||||
focusOnMouseOver?: boolean;
|
||||
};
|
||||
|
||||
// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
|
||||
export const RovingAccessibleButton = <T extends keyof JSX.IntrinsicElements>({
|
||||
export const RovingAccessibleButton = <T extends keyof HTMLElementTagNameMap>({
|
||||
inputRef,
|
||||
onFocus,
|
||||
onMouseOver,
|
||||
focusOnMouseOver,
|
||||
element,
|
||||
...props
|
||||
}: Props<T>): JSX.Element => {
|
||||
const [onFocusInternal, isActive, ref] = useRovingTabIndex(inputRef);
|
||||
const [onFocusInternal, isActive, ref] = useRovingTabIndex<HTMLElementTagNameMap[T]>(inputRef);
|
||||
return (
|
||||
<AccessibleButton
|
||||
{...props}
|
||||
element={element as keyof JSX.IntrinsicElements}
|
||||
onFocus={(event: React.FocusEvent) => {
|
||||
onFocus={(event: React.FocusEvent<never, never>) => {
|
||||
onFocusInternal();
|
||||
onFocus?.(event);
|
||||
}}
|
||||
onMouseOver={(event: React.MouseEvent) => {
|
||||
onMouseOver={(event: React.MouseEvent<never, never>) => {
|
||||
if (focusOnMouseOver) onFocusInternal();
|
||||
onMouseOver?.(event);
|
||||
}}
|
||||
|
||||
@@ -132,6 +132,7 @@ import { SessionLockStolenView } from "./auth/SessionLockStolenView";
|
||||
import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView";
|
||||
import { LoginSplashView } from "./auth/LoginSplashView";
|
||||
import { cleanUpDraftsIfRequired } from "../../DraftCleaner";
|
||||
import { ClientLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/ClientLifecycle";
|
||||
|
||||
// legacy export
|
||||
export { default as Views } from "../../Views";
|
||||
@@ -1553,6 +1554,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
|
||||
this.firstSyncComplete = true;
|
||||
this.firstSyncPromise.resolve();
|
||||
console.log('TEST!!!');
|
||||
ModuleRunner.instance.invoke(ClientLifecycle.FirstSync);
|
||||
|
||||
if (Notifier.shouldShowPrompt() && !MatrixClientPeg.userRegisteredWithinLastHours(24)) {
|
||||
showNotificationsToast(false);
|
||||
|
||||
@@ -43,6 +43,7 @@ import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePaylo
|
||||
import { SDKContext } from "../../contexts/SDKContext";
|
||||
import { shouldShowFeedback } from "../../utils/Feedback";
|
||||
import DarkLightModeSvg from "../../../res/img/element-icons/roomlist/dark-light-mode.svg";
|
||||
import { ModuleRunner } from "../../modules/ModuleRunner";
|
||||
|
||||
interface IProps {
|
||||
isPanelCollapsed: boolean;
|
||||
@@ -410,13 +411,17 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||
|
||||
const userId = MatrixClientPeg.safeGet().getSafeUserId();
|
||||
const displayName = OwnProfileStore.instance.displayName || userId;
|
||||
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||
let avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||
|
||||
let name: JSX.Element | undefined;
|
||||
if (!this.props.isPanelCollapsed) {
|
||||
name = <div className="mx_UserMenu_name">{displayName}</div>;
|
||||
}
|
||||
|
||||
if (!avatarUrl) {
|
||||
avatarUrl = ModuleRunner.instance.extensions.conference.defaultAvatarUrlForString(userId);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_UserMenu">
|
||||
<ContextMenuButton
|
||||
|
||||
@@ -11,7 +11,7 @@ import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import AuthPage from "../../views/auth/AuthPage";
|
||||
import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody";
|
||||
import CreateCrossSigningDialog from "../../views/dialogs/security/CreateCrossSigningDialog";
|
||||
import { InitialCryptoSetupDialog } from "../../views/dialogs/security/InitialCryptoSetupDialog";
|
||||
|
||||
interface IProps {
|
||||
matrixClient: MatrixClient;
|
||||
@@ -25,7 +25,7 @@ export default class E2eSetup extends React.Component<IProps> {
|
||||
return (
|
||||
<AuthPage>
|
||||
<CompleteSecurityBody>
|
||||
<CreateCrossSigningDialog
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={this.props.matrixClient}
|
||||
onFinished={this.props.onFinished}
|
||||
accountPassword={this.props.accountPassword}
|
||||
|
||||
@@ -235,12 +235,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||
value={this.state.password}
|
||||
disabled={this.state.busy}
|
||||
/>
|
||||
<AccessibleButton
|
||||
onClick={this.onPasswordLogin}
|
||||
kind="primary"
|
||||
type="submit"
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
<AccessibleButton onClick={this.onPasswordLogin} kind="primary" disabled={this.state.busy}>
|
||||
{_t("action|sign_in")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton onClick={this.onForgotPassword} kind="link">
|
||||
|
||||
@@ -910,7 +910,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
|
||||
|
||||
export class FallbackAuthEntry<T = {}> extends React.Component<IAuthEntryProps & T> {
|
||||
protected popupWindow: Window | null;
|
||||
protected fallbackButton = createRef<HTMLButtonElement>();
|
||||
protected fallbackButton = createRef<HTMLDivElement>();
|
||||
|
||||
public constructor(props: IAuthEntryProps & T) {
|
||||
super(props);
|
||||
|
||||
@@ -298,7 +298,7 @@ const SettingsList: React.FC<ISettingsListProps> = ({ onBack, onView, onEdit })
|
||||
<code>{i}</code>
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
alt={_t("devtools|edit_setting")}
|
||||
title={_t("devtools|edit_setting")}
|
||||
onClick={() => onEdit(i)}
|
||||
className="mx_DevTools_SettingsExplorer_edit"
|
||||
>
|
||||
|
||||
@@ -25,14 +25,19 @@ interface Props {
|
||||
}
|
||||
|
||||
/*
|
||||
* Walks the user through the process of creating a cross-signing keys. In most
|
||||
* cases, only a spinner is shown, but for more complex auth like SSO, the user
|
||||
* may need to complete some steps to proceed.
|
||||
* Walks the user through the process of creating a cross-signing keys.
|
||||
* In most cases, only a spinner is shown, but for more
|
||||
* complex auth like SSO, the user may need to complete some steps to proceed.
|
||||
*/
|
||||
const CreateCrossSigningDialog: React.FC<Props> = ({ matrixClient, accountPassword, tokenLogin, onFinished }) => {
|
||||
export const InitialCryptoSetupDialog: React.FC<Props> = ({
|
||||
matrixClient,
|
||||
accountPassword,
|
||||
tokenLogin,
|
||||
onFinished,
|
||||
}) => {
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const bootstrapCrossSigning = useCallback(async () => {
|
||||
const doSetup = useCallback(async () => {
|
||||
const cryptoApi = matrixClient.getCrypto();
|
||||
if (!cryptoApi) return;
|
||||
|
||||
@@ -40,6 +45,7 @@ const CreateCrossSigningDialog: React.FC<Props> = ({ matrixClient, accountPasswo
|
||||
|
||||
try {
|
||||
await createCrossSigning(matrixClient, tokenLogin, accountPassword);
|
||||
|
||||
onFinished(true);
|
||||
} catch (e) {
|
||||
if (tokenLogin) {
|
||||
@@ -58,8 +64,8 @@ const CreateCrossSigningDialog: React.FC<Props> = ({ matrixClient, accountPasswo
|
||||
}, [onFinished]);
|
||||
|
||||
useEffect(() => {
|
||||
bootstrapCrossSigning();
|
||||
}, [bootstrapCrossSigning]);
|
||||
doSetup();
|
||||
}, [doSetup]);
|
||||
|
||||
let content;
|
||||
if (error) {
|
||||
@@ -69,7 +75,7 @@ const CreateCrossSigningDialog: React.FC<Props> = ({ matrixClient, accountPasswo
|
||||
<div className="mx_Dialog_buttons">
|
||||
<DialogButtons
|
||||
primaryButton={_t("action|retry")}
|
||||
onPrimaryButtonClick={bootstrapCrossSigning}
|
||||
onPrimaryButtonClick={doSetup}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
</div>
|
||||
@@ -95,5 +101,3 @@ const CreateCrossSigningDialog: React.FC<Props> = ({ matrixClient, accountPasswo
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateCrossSigningDialog;
|
||||
@@ -1253,7 +1253,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||
<span>{filterToLabel(filter)}</span>
|
||||
<AccessibleButton
|
||||
tabIndex={-1}
|
||||
alt={_t("spotlight_dialog|remove_filter", {
|
||||
title={_t("spotlight_dialog|remove_filter", {
|
||||
filter: filterToLabel(filter),
|
||||
})}
|
||||
className="mx_SpotlightDialog_filter--close"
|
||||
|
||||
@@ -13,15 +13,15 @@ import { useRovingTabIndex } from "../../../../accessibility/RovingTabIndex";
|
||||
import AccessibleButton, { ButtonProps } from "../../elements/AccessibleButton";
|
||||
import { Ref } from "../../../../accessibility/roving/types";
|
||||
|
||||
type TooltipOptionProps<T extends keyof JSX.IntrinsicElements> = ButtonProps<T> & {
|
||||
type TooltipOptionProps<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
|
||||
className?: string;
|
||||
endAdornment?: ReactNode;
|
||||
inputRef?: Ref;
|
||||
};
|
||||
|
||||
export const TooltipOption = <T extends keyof JSX.IntrinsicElements>({
|
||||
export const TooltipOption = <T extends keyof HTMLElementTagNameMap>({
|
||||
inputRef,
|
||||
className,
|
||||
element,
|
||||
...props
|
||||
}: TooltipOptionProps<T>): JSX.Element => {
|
||||
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
|
||||
@@ -34,7 +34,6 @@ export const TooltipOption = <T extends keyof JSX.IntrinsicElements>({
|
||||
tabIndex={-1}
|
||||
aria-selected={isActive}
|
||||
role="option"
|
||||
element={element as keyof JSX.IntrinsicElements}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import TextInputDialog from "../dialogs/TextInputDialog";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import withValidation from "../elements/Validation";
|
||||
import { ModuleRunner } from "../../../modules/ModuleRunner";
|
||||
|
||||
const SETTING_NAME = "room_directory_servers";
|
||||
|
||||
@@ -122,13 +123,17 @@ function useServers(): ServerList {
|
||||
removeAll(removableServers, homeServer);
|
||||
removeAll(removableServers, ...configServers);
|
||||
|
||||
const allServers = [
|
||||
// we always show our connected HS, this takes precedence over it being configured or user-defined
|
||||
homeServer,
|
||||
...Array.from(configServers).sort(),
|
||||
...Array.from(removableServers).sort(),
|
||||
]
|
||||
|
||||
|
||||
|
||||
return {
|
||||
allServers: [
|
||||
// we always show our connected HS, this takes precedence over it being configured or user-defined
|
||||
homeServer,
|
||||
...Array.from(configServers).sort(),
|
||||
...Array.from(removableServers).sort(),
|
||||
],
|
||||
allServers: ModuleRunner.instance.extensions.conference.filterServerList(allServers),
|
||||
homeServer,
|
||||
userDefinedServers: Array.from(removableServers).sort(),
|
||||
setUserDefinedServers,
|
||||
@@ -168,7 +173,7 @@ export const NetworkDropdown: React.FC<IProps> = ({ protocols, config, setConfig
|
||||
adornment: (
|
||||
<AccessibleButton
|
||||
className="mx_NetworkDropdown_removeServer"
|
||||
alt={_t("spotlight|public_rooms|network_dropdown_remove_server_adornment", { roomServer })}
|
||||
title={_t("spotlight|public_rooms|network_dropdown_remove_server_adornment", { roomServer })}
|
||||
onClick={() => setUserDefinedServers(without(userDefinedServers, roomServer))}
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -6,7 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps, forwardRef, FunctionComponent, HTMLAttributes, InputHTMLAttributes, Ref } from "react";
|
||||
import React, {
|
||||
ComponentProps,
|
||||
ComponentPropsWithoutRef,
|
||||
forwardRef,
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
KeyboardEvent,
|
||||
Ref,
|
||||
} from "react";
|
||||
import classnames from "classnames";
|
||||
import { Tooltip } from "@vector-im/compound-web";
|
||||
|
||||
@@ -38,20 +46,8 @@ export type AccessibleButtonKind =
|
||||
| "icon_primary"
|
||||
| "icon_primary_outline";
|
||||
|
||||
/**
|
||||
* This type construct allows us to specifically pass those props down to the element we’re creating that the element
|
||||
* actually supports.
|
||||
*
|
||||
* e.g., if element is set to "a", we’ll support href and target, if it’s set to "input", we support type.
|
||||
*
|
||||
* To remain compatible with existing code, we’ll continue to support InputHTMLAttributes<Element>
|
||||
*/
|
||||
type DynamicHtmlElementProps<T extends keyof JSX.IntrinsicElements> =
|
||||
JSX.IntrinsicElements[T] extends HTMLAttributes<{}> ? DynamicElementProps<T> : DynamicElementProps<"div">;
|
||||
type DynamicElementProps<T extends keyof JSX.IntrinsicElements> = Partial<
|
||||
Omit<JSX.IntrinsicElements[T], "ref" | "onClick" | "onMouseDown" | "onKeyUp" | "onKeyDown">
|
||||
> &
|
||||
Omit<InputHTMLAttributes<Element>, "onClick">;
|
||||
type ElementType = keyof HTMLElementTagNameMap;
|
||||
const defaultElement = "div";
|
||||
|
||||
type TooltipProps = ComponentProps<typeof Tooltip>;
|
||||
|
||||
@@ -60,7 +56,7 @@ type TooltipProps = ComponentProps<typeof Tooltip>;
|
||||
*
|
||||
* Extends props accepted by the underlying element specified using the `element` prop.
|
||||
*/
|
||||
type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> & {
|
||||
type Props<T extends ElementType = "div"> = {
|
||||
/**
|
||||
* The base element type. "div" by default.
|
||||
*/
|
||||
@@ -105,14 +101,12 @@ type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> &
|
||||
disableTooltip?: TooltipProps["disabled"];
|
||||
};
|
||||
|
||||
export type ButtonProps<T extends keyof JSX.IntrinsicElements> = Props<T>;
|
||||
export type ButtonProps<T extends ElementType> = Props<T> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>;
|
||||
|
||||
/**
|
||||
* Type of the props passed to the element that is rendered by AccessibleButton.
|
||||
*/
|
||||
interface RenderedElementProps extends React.InputHTMLAttributes<Element> {
|
||||
ref?: React.Ref<Element>;
|
||||
}
|
||||
type RenderedElementProps<T extends ElementType> = React.InputHTMLAttributes<Element> & RefProp<T>;
|
||||
|
||||
/**
|
||||
* AccessibleButton is a generic wrapper for any element that should be treated
|
||||
@@ -124,9 +118,9 @@ interface RenderedElementProps extends React.InputHTMLAttributes<Element> {
|
||||
* @param {Object} props react element properties
|
||||
* @returns {Object} rendered react
|
||||
*/
|
||||
const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
|
||||
const AccessibleButton = forwardRef(function <T extends ElementType = typeof defaultElement>(
|
||||
{
|
||||
element = "div" as T,
|
||||
element,
|
||||
onClick,
|
||||
children,
|
||||
kind,
|
||||
@@ -141,10 +135,10 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
|
||||
onTooltipOpenChange,
|
||||
disableTooltip,
|
||||
...restProps
|
||||
}: Props<T>,
|
||||
ref: Ref<HTMLElement>,
|
||||
}: ButtonProps<T>,
|
||||
ref: Ref<HTMLElementTagNameMap[T]>,
|
||||
): JSX.Element {
|
||||
const newProps: RenderedElementProps = restProps;
|
||||
const newProps = restProps as RenderedElementProps<T>;
|
||||
newProps["aria-label"] = newProps["aria-label"] ?? title;
|
||||
if (disabled) {
|
||||
newProps["aria-disabled"] = true;
|
||||
@@ -162,7 +156,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
|
||||
// And divs which we report as role button to assistive technologies.
|
||||
// Browsers handle space and enter key presses differently and we are only adjusting to the
|
||||
// inconsistencies here
|
||||
newProps.onKeyDown = (e) => {
|
||||
newProps.onKeyDown = (e: KeyboardEvent<never>) => {
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
|
||||
switch (action) {
|
||||
@@ -178,7 +172,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
|
||||
onKeyDown?.(e);
|
||||
}
|
||||
};
|
||||
newProps.onKeyUp = (e) => {
|
||||
newProps.onKeyUp = (e: KeyboardEvent<never>) => {
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
|
||||
switch (action) {
|
||||
@@ -207,7 +201,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
|
||||
});
|
||||
|
||||
// React.createElement expects InputHTMLAttributes
|
||||
const button = React.createElement(element, newProps, children);
|
||||
const button = React.createElement(element ?? defaultElement, newProps, children);
|
||||
|
||||
if (title) {
|
||||
return (
|
||||
@@ -233,4 +227,15 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
|
||||
};
|
||||
(AccessibleButton as FunctionComponent).displayName = "AccessibleButton";
|
||||
|
||||
export default AccessibleButton;
|
||||
interface RefProp<T extends ElementType> {
|
||||
ref?: Ref<HTMLElementTagNameMap[T]>;
|
||||
}
|
||||
|
||||
interface ButtonComponent {
|
||||
// With the explicit `element` prop
|
||||
<C extends ElementType>(props: { element?: C } & ButtonProps<C> & RefProp<C>): ReactElement;
|
||||
// Without the explicit `element` prop
|
||||
(props: ButtonProps<"div"> & RefProp<"div">): ReactElement;
|
||||
}
|
||||
|
||||
export default AccessibleButton as ButtonComponent;
|
||||
|
||||
@@ -133,12 +133,7 @@ export default class EditableItemList<P = {}> extends React.PureComponent<IProps
|
||||
onChange={this.onNewItemChanged}
|
||||
list={this.props.suggestionsListId}
|
||||
/>
|
||||
<AccessibleButton
|
||||
onClick={this.onItemAdded}
|
||||
kind="primary"
|
||||
type="submit"
|
||||
disabled={!this.props.newItem}
|
||||
>
|
||||
<AccessibleButton onClick={this.onItemAdded} kind="primary" disabled={!this.props.newItem}>
|
||||
{_t("action|add")}
|
||||
</AccessibleButton>
|
||||
</form>
|
||||
|
||||
@@ -16,6 +16,7 @@ import SdkConfig from "../../../SdkConfig";
|
||||
import Modal from "../../../Modal";
|
||||
import ServerPickerDialog from "../dialogs/ServerPickerDialog";
|
||||
import InfoDialog from "../dialogs/InfoDialog";
|
||||
import { ModuleRunner } from "../../../modules/ModuleRunner";
|
||||
|
||||
interface IProps {
|
||||
title?: string;
|
||||
@@ -77,7 +78,10 @@ const ServerPicker: React.FC<IProps> = ({ title, dialogTitle, serverConfig, onSe
|
||||
}
|
||||
|
||||
let desc;
|
||||
if (serverConfig.hsName === "matrix.org") {
|
||||
const moduleDesc = ModuleRunner.instance.extensions.conference.serverLoginNotice(serverConfig.hsName);
|
||||
if (moduleDesc) {
|
||||
desc = <span className="mx_ServerPicker_desc">{moduleDesc}</span>;
|
||||
} else if (serverConfig.hsName === "matrix.org") {
|
||||
desc = <span className="mx_ServerPicker_desc">{_t("auth|server_picker_description_matrix.org")}</span>;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class Emoji extends React.PureComponent<IProps> {
|
||||
return (
|
||||
<RovingAccessibleButton
|
||||
id={this.props.id}
|
||||
onClick={(ev) => onClick(ev, emoji)}
|
||||
onClick={(ev: ButtonEvent) => onClick(ev, emoji)}
|
||||
onMouseEnter={() => onMouseEnter(emoji)}
|
||||
onMouseLeave={() => onMouseLeave(emoji)}
|
||||
className="mx_EmojiPicker_item_wrapper"
|
||||
|
||||
@@ -90,7 +90,7 @@ export const MPollEndBody = React.forwardRef<any, IBodyProps>(({ mxEvent, ...pro
|
||||
const { pollStartEvent, isLoadingPollStartEvent } = usePollStartEvent(mxEvent);
|
||||
|
||||
if (!pollStartEvent) {
|
||||
const pollEndFallbackMessage = M_TEXT.findIn(mxEvent.getContent()) || textForEvent(mxEvent, cli);
|
||||
const pollEndFallbackMessage = M_TEXT.findIn<string>(mxEvent.getContent()) || textForEvent(mxEvent, cli);
|
||||
return (
|
||||
<>
|
||||
<PollIcon className="mx_MPollEndBody_icon" />
|
||||
|
||||
@@ -435,7 +435,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||
<RovingAccessibleButton
|
||||
className="mx_MessageActionBar_iconButton"
|
||||
title={isPinned ? _t("action|unpin") : _t("action|pin")}
|
||||
onClick={(e) => this.onPinClick(e, isPinned)}
|
||||
onClick={(e: ButtonEvent) => this.onPinClick(e, isPinned)}
|
||||
onContextMenu={(e: ButtonEvent) => this.onPinClick(e, isPinned)}
|
||||
key="pin"
|
||||
placement="left"
|
||||
|
||||
@@ -128,7 +128,8 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||
if (!this.props.editState) {
|
||||
const stoppedEditing = prevProps.editState && !this.props.editState;
|
||||
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
|
||||
if (messageWasEdited || stoppedEditing) {
|
||||
const urlPreviewChanged = prevProps.showUrlPreview !== this.props.showUrlPreview;
|
||||
if (messageWasEdited || stoppedEditing || urlPreviewChanged) {
|
||||
this.applyFormatting();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +407,6 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||
forceValidity={this.state.error ? false : undefined}
|
||||
/>
|
||||
<AccessibleButton
|
||||
type="submit"
|
||||
kind="primary_sm"
|
||||
onClick={this.checkIdServer}
|
||||
disabled={!this.idServerChangeEnabled()}
|
||||
|
||||
@@ -7,23 +7,21 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import React, { ComponentProps } from "react";
|
||||
import React from "react";
|
||||
import { ChevronDownIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import AccessibleButton from "../../elements/AccessibleButton";
|
||||
import AccessibleButton, { ButtonProps } from "../../elements/AccessibleButton";
|
||||
|
||||
type Props<T extends keyof JSX.IntrinsicElements> = Omit<
|
||||
ComponentProps<typeof AccessibleButton<T>>,
|
||||
"aria-label" | "title" | "kind" | "className" | "onClick" | "element"
|
||||
type Props<T extends keyof HTMLElementTagNameMap> = Omit<
|
||||
ButtonProps<T>,
|
||||
"aria-label" | "title" | "kind" | "className" | "element"
|
||||
> & {
|
||||
isExpanded: boolean;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export const DeviceExpandDetailsButton = <T extends keyof JSX.IntrinsicElements>({
|
||||
export const DeviceExpandDetailsButton = <T extends keyof HTMLElementTagNameMap>({
|
||||
isExpanded,
|
||||
onClick,
|
||||
...rest
|
||||
}: Props<T>): JSX.Element => {
|
||||
const label = isExpanded ? _t("settings|sessions|hide_details") : _t("settings|sessions|show_details");
|
||||
@@ -36,7 +34,6 @@ export const DeviceExpandDetailsButton = <T extends keyof JSX.IntrinsicElements>
|
||||
className={classNames("mx_DeviceExpandDetailsButton", {
|
||||
mx_DeviceExpandDetailsButton_expanded: isExpanded,
|
||||
})}
|
||||
onClick={onClick}
|
||||
>
|
||||
<ChevronDownIcon className="mx_DeviceExpandDetailsButton_icon" />
|
||||
</AccessibleButton>
|
||||
|
||||
@@ -268,7 +268,6 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
||||
onChange={this.onPersonalRuleChanged}
|
||||
/>
|
||||
<AccessibleButton
|
||||
type="submit"
|
||||
kind="primary"
|
||||
onClick={this.onAddPersonalRule}
|
||||
disabled={this.state.busy}
|
||||
@@ -295,12 +294,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
||||
value={this.state.newList}
|
||||
onChange={this.onNewListChanged}
|
||||
/>
|
||||
<AccessibleButton
|
||||
type="submit"
|
||||
kind="primary"
|
||||
onClick={this.onSubscribeList}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
<AccessibleButton kind="primary" onClick={this.onSubscribeList} disabled={this.state.busy}>
|
||||
{_t("action|subscribe")}
|
||||
</AccessibleButton>
|
||||
</form>
|
||||
|
||||
@@ -71,7 +71,6 @@ export const SpaceAvatar: React.FC<Pick<IProps, "avatarUrl" | "avatarDisabled" |
|
||||
<AccessibleButton
|
||||
className="mx_SpaceBasicSettings_avatar"
|
||||
onClick={() => avatarUploadRef.current?.click()}
|
||||
alt=""
|
||||
/>
|
||||
<AccessibleButton
|
||||
onClick={() => avatarUploadRef.current?.click()}
|
||||
|
||||
@@ -221,7 +221,7 @@ const CreateSpaceButton: React.FC<Pick<IInnerSpacePanelProps, "isPanelCollapsed"
|
||||
isPanelCollapsed,
|
||||
setPanelCollapsed,
|
||||
}) => {
|
||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLElement>();
|
||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPanelCollapsed && menuDisplayed) {
|
||||
|
||||
@@ -30,7 +30,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||
import { toRightOf, useContextMenu } from "../../structures/ContextMenu";
|
||||
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import AccessibleButton, { ButtonEvent, ButtonProps as AccessibleButtonProps } from "../elements/AccessibleButton";
|
||||
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
|
||||
import { NotificationLevel } from "../../../stores/notifications/NotificationLevel";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
@@ -39,8 +39,8 @@ import SpaceContextMenu from "../context_menus/SpaceContextMenu";
|
||||
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
type ButtonProps<T extends keyof JSX.IntrinsicElements> = Omit<
|
||||
ComponentProps<typeof AccessibleButton<T>>,
|
||||
type ButtonProps<T extends keyof HTMLElementTagNameMap> = Omit<
|
||||
AccessibleButtonProps<T>,
|
||||
"title" | "onClick" | "size" | "element"
|
||||
> & {
|
||||
space?: Room;
|
||||
@@ -52,12 +52,12 @@ type ButtonProps<T extends keyof JSX.IntrinsicElements> = Omit<
|
||||
notificationState?: NotificationState;
|
||||
isNarrow?: boolean;
|
||||
size: string;
|
||||
innerRef?: RefObject<HTMLElement>;
|
||||
innerRef?: RefObject<HTMLDivElement>;
|
||||
ContextMenuComponent?: ComponentType<ComponentProps<typeof SpaceContextMenu>>;
|
||||
onClick?(ev?: ButtonEvent): void;
|
||||
};
|
||||
|
||||
export const SpaceButton = <T extends keyof JSX.IntrinsicElements>({
|
||||
export const SpaceButton = <T extends keyof HTMLElementTagNameMap>({
|
||||
space,
|
||||
spaceKey: _spaceKey,
|
||||
className,
|
||||
@@ -72,8 +72,8 @@ export const SpaceButton = <T extends keyof JSX.IntrinsicElements>({
|
||||
ContextMenuComponent,
|
||||
...props
|
||||
}: ButtonProps<T>): JSX.Element => {
|
||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLElement>(innerRef);
|
||||
const [onFocus, isActive, ref] = useRovingTabIndex(handle);
|
||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>(innerRef);
|
||||
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLDivElement>(handle);
|
||||
const tabIndex = isActive ? 0 : -1;
|
||||
|
||||
const spaceKey = _spaceKey ?? space?.roomId;
|
||||
|
||||
@@ -69,7 +69,7 @@ interface IDropdownButtonProps extends ButtonProps {
|
||||
}
|
||||
|
||||
const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, deviceKinds, ...props }) => {
|
||||
const [menuDisplayed, buttonRef, openMenu, closeMenu] = useContextMenu();
|
||||
const [menuDisplayed, buttonRef, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
|
||||
const [hoveringDropdown, setHoveringDropdown] = useState(false);
|
||||
|
||||
const classes = classNames("mx_LegacyCallViewButtons_button", "mx_LegacyCallViewButtons_dropdownButton", {
|
||||
|
||||
@@ -2936,6 +2936,7 @@
|
||||
"warning": "<w>WARNUNG:</w> <description/>"
|
||||
},
|
||||
"share": {
|
||||
"link_copied": "Link kopiert",
|
||||
"permalink_message": "Link zur ausgewählten Nachricht",
|
||||
"permalink_most_recent": "Link zur aktuellsten Nachricht",
|
||||
"share_call": "Konferenzeinladungslink",
|
||||
|
||||
@@ -398,7 +398,7 @@
|
||||
},
|
||||
"bug_reporting": {
|
||||
"additional_context": "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.",
|
||||
"before_submitting": "Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.",
|
||||
"before_submitting": "We recommend <a>creating a GitHub issue</a> to ensure that your report is reviewed.",
|
||||
"collecting_information": "Collecting app version information",
|
||||
"collecting_logs": "Collecting logs",
|
||||
"create_new_issue": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
|
||||
|
||||
@@ -340,7 +340,7 @@
|
||||
"set_email_prompt": "Czy chcesz ustawić adres e-mail?",
|
||||
"sign_in_description": "Użyj swojego konta, aby kontynuować.",
|
||||
"sign_in_instead": "Zamiast tego zaloguj się",
|
||||
"sign_in_instead_prompt": "Zamiast tego zaloguj się",
|
||||
"sign_in_instead_prompt": "Masz już konto? <a>Zaloguj się tutaj</a>",
|
||||
"sign_in_or_register": "Zaloguj się lub utwórz konto",
|
||||
"sign_in_or_register_description": "Użyj konta lub utwórz nowe, aby kontynuować.",
|
||||
"sign_in_prompt": "Posiadasz już konto? <a>Zaloguj się</a>",
|
||||
@@ -505,6 +505,7 @@
|
||||
"matrix": "Matrix",
|
||||
"message": "Wiadomość",
|
||||
"message_layout": "Wygląd wiadomości",
|
||||
"message_timestamp_invalid": "Nieprawidłowy znacznik czasu",
|
||||
"microphone": "Mikrofon",
|
||||
"model": "Model",
|
||||
"modern": "Współczesny",
|
||||
@@ -908,6 +909,8 @@
|
||||
"warning": "Jeżeli nie ustawiłeś nowej metody odzyskiwania, atakujący może uzyskać dostęp do Twojego konta. Zmień hasło konta i natychmiast ustaw nową metodę odzyskiwania w Ustawieniach."
|
||||
},
|
||||
"not_supported": "<niewspierany>",
|
||||
"pinned_identity_changed": "Tożsamość użytkownika %(displayName)s (<b>%(userId)s</b>) uległa zmianie. <a>Dowiedz się więcej</a>",
|
||||
"pinned_identity_changed_no_displayname": "Tożsamość użytkownika <b>%(userId)s</b> uległa zmianie <a>Dowiedz się więcej</a>",
|
||||
"recovery_method_removed": {
|
||||
"description_1": "Ta sesja wykryła, że Twoja fraza bezpieczeństwa i klucz dla bezpiecznych wiadomości zostały usunięte.",
|
||||
"description_2": "Jeśli zrobiłeś to przez pomyłkę, możesz ustawić bezpieczne wiadomości w tej sesji, co zaszyfruje ponownie historię wiadomości za pomocą nowej metody odzyskiwania.",
|
||||
@@ -996,7 +999,7 @@
|
||||
"unverified_sessions_toast_description": "Sprawdź, by upewnić się że Twoje konto jest bezpieczne",
|
||||
"unverified_sessions_toast_reject": "Później",
|
||||
"unverified_sessions_toast_title": "Masz niezweryfikowane sesje",
|
||||
"verification_description": "Zweryfikuj swoją tożsamość, aby uzyskać dostęp do wiadomości szyfrowanych i potwierdzić swoją tożsamość innym.",
|
||||
"verification_description": "Zweryfikuj swoją tożsamość, aby uzyskać dostęp do wiadomości szyfrowanych i potwierdzić swoją tożsamość innym. Jeśli korzystasz z urządzenia mobilnego, otwórz na niej aplikację.",
|
||||
"verification_dialog_title_device": "Zweryfikuj drugie urządzenie",
|
||||
"verification_dialog_title_user": "Żądanie weryfikacji",
|
||||
"verification_skip_warning": "Bez weryfikacji, nie będziesz posiadać dostępu do wszystkich swoich wiadomości, a inni będą Cię widzieć jako niezaufanego.",
|
||||
@@ -1102,7 +1105,15 @@
|
||||
"you": "Dodano reakcję %(reaction)s do %(message)s"
|
||||
},
|
||||
"m.sticker": "%(senderName)s: %(stickerName)s",
|
||||
"m.text": "%(senderName)s: %(message)s"
|
||||
"m.text": "%(senderName)s: %(message)s",
|
||||
"prefix": {
|
||||
"audio": "Audio",
|
||||
"file": "Plik",
|
||||
"image": "Obraz",
|
||||
"poll": "Ankieta",
|
||||
"video": "Wideo"
|
||||
},
|
||||
"preview": "<bold>%(prefix)s:</bold> %(preview)s"
|
||||
},
|
||||
"export_chat": {
|
||||
"cancelled": "Eksport został anulowany",
|
||||
@@ -2939,6 +2950,7 @@
|
||||
"warning": "<w>OSTRZEŻENIE:</w> <description/>"
|
||||
},
|
||||
"share": {
|
||||
"link_copied": "Skopiowano link",
|
||||
"permalink_message": "Link do zaznaczonej wiadomości",
|
||||
"permalink_most_recent": "Link do najnowszej wiadomości",
|
||||
"share_call": "Link zaproszenia do konferencji",
|
||||
@@ -3247,8 +3259,8 @@
|
||||
"historical_event_no_key_backup": "Historia wiadomości nie jest dostępna na tym urządzeniu",
|
||||
"historical_event_unverified_device": "Musisz zweryfikować to urządzenie, aby wyświetlić historię wiadomości",
|
||||
"historical_event_user_not_joined": "Nie masz dostępu do tej wiadomości",
|
||||
"sender_identity_previously_verified": "Zweryfikowana tożsamość uległa zmianie",
|
||||
"sender_unsigned_device": "Zaszyfrowano przez urządzenie niezweryfikowane przez właściciela.",
|
||||
"sender_identity_previously_verified": "Zweryfikowana tożsamość nadawcy uległa zmianie",
|
||||
"sender_unsigned_device": "Wysłano z niezabezpieczonego urządzenia.",
|
||||
"unable_to_decrypt": "Nie można rozszyfrować wiadomości"
|
||||
},
|
||||
"disambiguated_profile": "%(displayName)s (%(matrixId)s)",
|
||||
@@ -3720,6 +3732,7 @@
|
||||
"error_files_too_large": "Te pliki są <b>zbyt duże</b> do wysłania. Ograniczenie wielkości plików to %(limit)s.",
|
||||
"error_some_files_too_large": "Niektóre pliki są <b>zbyt duże</b> do wysłania. Ograniczenie wielkości plików to %(limit)s.",
|
||||
"error_title": "Błąd wysyłania",
|
||||
"not_image": "Wybrany plik nie jest prawidłowym plikiem obrazu.",
|
||||
"title": "Prześlij pliki",
|
||||
"title_progress": "Prześlij pliki (%(current)s z %(total)s)",
|
||||
"upload_all_button": "Prześlij wszystko",
|
||||
|
||||
@@ -17,6 +17,10 @@ import {
|
||||
DefaultExperimentalExtensions,
|
||||
ProvideExperimentalExtensions,
|
||||
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions";
|
||||
import {
|
||||
DefaultConferenceExtensions,
|
||||
ProvideConferenceExtensions,
|
||||
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/ConferenceExtensions";
|
||||
|
||||
import { AppModule } from "./AppModule";
|
||||
import { ModuleFactory } from "./ModuleFactory";
|
||||
@@ -30,6 +34,7 @@ class ExtensionsManager {
|
||||
// Private backing fields for extensions
|
||||
private cryptoSetupExtension: ProvideCryptoSetupExtensions;
|
||||
private experimentalExtension: ProvideExperimentalExtensions;
|
||||
private conferenceExtension: ProvideConferenceExtensions;
|
||||
|
||||
/** `true` if `cryptoSetupExtension` is the default implementation; `false` if it is implemented by a module. */
|
||||
private hasDefaultCryptoSetupExtension = true;
|
||||
@@ -37,6 +42,9 @@ class ExtensionsManager {
|
||||
/** `true` if `experimentalExtension` is the default implementation; `false` if it is implemented by a module. */
|
||||
private hasDefaultExperimentalExtension = true;
|
||||
|
||||
/** `true` if `conferenceExtension` is the default implementation; `false` if it is implemented by a module. */
|
||||
private hasDefaultConferenceExtension = true;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*/
|
||||
@@ -44,6 +52,7 @@ class ExtensionsManager {
|
||||
// Set up defaults
|
||||
this.cryptoSetupExtension = new DefaultCryptoSetupExtensions();
|
||||
this.experimentalExtension = new DefaultExperimentalExtensions();
|
||||
this.conferenceExtension = new DefaultConferenceExtensions();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,6 +76,15 @@ class ExtensionsManager {
|
||||
return this.experimentalExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides extensions useful to conferences.
|
||||
*
|
||||
* @returns The registered extension. If no module provides this extension, a default implementation is returned.
|
||||
*/
|
||||
public get conference(): ProvideConferenceExtensions {
|
||||
return this.conferenceExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add any extensions provided by the module.
|
||||
*
|
||||
@@ -100,6 +118,18 @@ class ExtensionsManager {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add the experimental extension if any */
|
||||
if (runtimeModule.extensions?.conference) {
|
||||
if (this.hasDefaultConferenceExtension) {
|
||||
this.conferenceExtension = runtimeModule.extensions.conference;
|
||||
this.hasDefaultConferenceExtension = false;
|
||||
} else {
|
||||
throw new Error(
|
||||
`adding conference extension implementation from module ${runtimeModule.moduleName} but an implementation was already provided.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||
EventType.CallSDPStreamMetadataChanged,
|
||||
EventType.CallSDPStreamMetadataChangedPrefix,
|
||||
EventType.CallReplaces,
|
||||
EventType.CallEncryptionKeysPrefix,
|
||||
];
|
||||
for (const eventType of sendRecvToDevice) {
|
||||
this.allowedCapabilities.add(
|
||||
|
||||
@@ -12,14 +12,14 @@ import { mocked } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { createCrossSigning } from "../../../../../src/CreateCrossSigning";
|
||||
import CreateCrossSigningDialog from "../../../../../src/components/views/dialogs/security/CreateCrossSigningDialog";
|
||||
import { InitialCryptoSetupDialog } from "../../../../../src/components/views/dialogs/security/InitialCryptoSetupDialog";
|
||||
import { createTestClient } from "../../../../test-utils";
|
||||
|
||||
jest.mock("../../../../../src/CreateCrossSigning", () => ({
|
||||
createCrossSigning: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("CreateCrossSigningDialog", () => {
|
||||
describe("InitialCryptoSetupDialog", () => {
|
||||
let client: MatrixClient;
|
||||
let createCrossSigningResolve: () => void;
|
||||
let createCrossSigningReject: (e: Error) => void;
|
||||
@@ -43,7 +43,7 @@ describe("CreateCrossSigningDialog", () => {
|
||||
const onFinished = jest.fn();
|
||||
|
||||
render(
|
||||
<CreateCrossSigningDialog
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={client}
|
||||
accountPassword="hunter2"
|
||||
tokenLogin={false}
|
||||
@@ -61,7 +61,7 @@ describe("CreateCrossSigningDialog", () => {
|
||||
|
||||
it("should display an error if createCrossSigning fails", async () => {
|
||||
render(
|
||||
<CreateCrossSigningDialog
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={client}
|
||||
accountPassword="hunter2"
|
||||
tokenLogin={false}
|
||||
@@ -78,7 +78,7 @@ describe("CreateCrossSigningDialog", () => {
|
||||
const onFinished = jest.fn();
|
||||
|
||||
render(
|
||||
<CreateCrossSigningDialog
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={client}
|
||||
accountPassword="hunter2"
|
||||
tokenLogin={true}
|
||||
@@ -95,7 +95,7 @@ describe("CreateCrossSigningDialog", () => {
|
||||
const onFinished = jest.fn();
|
||||
|
||||
render(
|
||||
<CreateCrossSigningDialog
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={client}
|
||||
accountPassword="hunter2"
|
||||
tokenLogin={false}
|
||||
@@ -113,7 +113,7 @@ describe("CreateCrossSigningDialog", () => {
|
||||
|
||||
it("should retry when the retry button is clicked", async () => {
|
||||
render(
|
||||
<CreateCrossSigningDialog
|
||||
<InitialCryptoSetupDialog
|
||||
matrixClient={client}
|
||||
accountPassword="hunter2"
|
||||
tokenLogin={false}
|
||||
@@ -314,7 +314,6 @@ exports[`<MatrixChat /> with a soft-logged-out session should show the soft-logo
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
Sign in
|
||||
</div>
|
||||
|
||||
@@ -375,55 +375,73 @@ describe("<TextualBody />", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("renders url previews correctly", () => {
|
||||
languageHandler.setMissingEntryGenerator((key) => key.split("|", 2)[1]);
|
||||
describe("url preview", () => {
|
||||
let matrixClient: MatrixClient;
|
||||
|
||||
const matrixClient = getMockClientWithEventEmitter({
|
||||
getRoom: () => mkStubRoom("room_id", "room name", undefined),
|
||||
getAccountData: (): MatrixClient | undefined => undefined,
|
||||
getUrlPreview: (url: string) => new Promise(() => {}),
|
||||
isGuest: () => false,
|
||||
mxcUrlToHttp: (s: string) => s,
|
||||
beforeEach(() => {
|
||||
languageHandler.setMissingEntryGenerator((key) => key.split("|", 2)[1]);
|
||||
matrixClient = getMockClientWithEventEmitter({
|
||||
getRoom: () => mkStubRoom("room_id", "room name", undefined),
|
||||
getAccountData: (): MatrixClient | undefined => undefined,
|
||||
getUrlPreview: (url: string) => new Promise(() => {}),
|
||||
isGuest: () => false,
|
||||
mxcUrlToHttp: (s: string) => s,
|
||||
});
|
||||
DMRoomMap.makeShared(defaultMatrixClient);
|
||||
});
|
||||
DMRoomMap.makeShared(defaultMatrixClient);
|
||||
|
||||
const ev = mkRoomTextMessage("Visit https://matrix.org/");
|
||||
const { container, rerender } = getComponent(
|
||||
{ mxEvent: ev, showUrlPreview: true, onHeightChanged: jest.fn() },
|
||||
matrixClient,
|
||||
);
|
||||
it("renders url previews correctly", () => {
|
||||
const ev = mkRoomTextMessage("Visit https://matrix.org/");
|
||||
const { container, rerender } = getComponent(
|
||||
{ mxEvent: ev, showUrlPreview: true, onHeightChanged: jest.fn() },
|
||||
matrixClient,
|
||||
);
|
||||
|
||||
expect(container).toHaveTextContent(ev.getContent().body);
|
||||
expect(container.querySelector("a")).toHaveAttribute("href", "https://matrix.org/");
|
||||
expect(container).toHaveTextContent(ev.getContent().body);
|
||||
expect(container.querySelector("a")).toHaveAttribute("href", "https://matrix.org/");
|
||||
|
||||
// simulate an event edit and check the transition from the old URL preview to the new one
|
||||
const ev2 = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "room_id",
|
||||
user: "sender",
|
||||
content: {
|
||||
"m.new_content": {
|
||||
body: "Visit https://vector.im/ and https://riot.im/",
|
||||
msgtype: "m.text",
|
||||
// simulate an event edit and check the transition from the old URL preview to the new one
|
||||
const ev2 = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "room_id",
|
||||
user: "sender",
|
||||
content: {
|
||||
"m.new_content": {
|
||||
body: "Visit https://vector.im/ and https://riot.im/",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
event: true,
|
||||
});
|
||||
jest.spyOn(ev, "replacingEventDate").mockReturnValue(new Date(1993, 7, 3));
|
||||
ev.makeReplaced(ev2);
|
||||
|
||||
getComponent(
|
||||
{ mxEvent: ev, showUrlPreview: true, onHeightChanged: jest.fn(), replacingEventId: ev.getId() },
|
||||
matrixClient,
|
||||
rerender,
|
||||
);
|
||||
|
||||
expect(container).toHaveTextContent(ev2.getContent()["m.new_content"].body + "(edited)");
|
||||
|
||||
const links = ["https://vector.im/", "https://riot.im/"];
|
||||
const anchorNodes = container.querySelectorAll("a");
|
||||
Array.from(anchorNodes).forEach((node, index) => {
|
||||
expect(node).toHaveAttribute("href", links[index]);
|
||||
});
|
||||
});
|
||||
jest.spyOn(ev, "replacingEventDate").mockReturnValue(new Date(1993, 7, 3));
|
||||
ev.makeReplaced(ev2);
|
||||
|
||||
getComponent(
|
||||
{ mxEvent: ev, showUrlPreview: true, onHeightChanged: jest.fn(), replacingEventId: ev.getId() },
|
||||
matrixClient,
|
||||
rerender,
|
||||
);
|
||||
it("should listen to showUrlPreview change", () => {
|
||||
const ev = mkRoomTextMessage("Visit https://matrix.org/");
|
||||
|
||||
expect(container).toHaveTextContent(ev2.getContent()["m.new_content"].body + "(edited)");
|
||||
const { container, rerender } = getComponent(
|
||||
{ mxEvent: ev, showUrlPreview: false, onHeightChanged: jest.fn() },
|
||||
matrixClient,
|
||||
);
|
||||
expect(container.querySelector(".mx_LinkPreviewGroup")).toBeNull();
|
||||
|
||||
const links = ["https://vector.im/", "https://riot.im/"];
|
||||
const anchorNodes = container.querySelectorAll("a");
|
||||
Array.from(anchorNodes).forEach((node, index) => {
|
||||
expect(node).toHaveAttribute("href", links[index]);
|
||||
getComponent({ mxEvent: ev, showUrlPreview: true, onHeightChanged: jest.fn() }, matrixClient, rerender);
|
||||
expect(container.querySelector(".mx_LinkPreviewGroup")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ describe("SetIntegrationManager", () => {
|
||||
deleteThreePid: jest.fn(),
|
||||
});
|
||||
|
||||
let stores: SdkContextClass;
|
||||
let stores!: SdkContextClass;
|
||||
|
||||
const getComponent = () => (
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
|
||||
@@ -85,7 +85,6 @@ exports[`<MjolnirUserSettingsTab /> renders correctly when user has no ignored u
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
Ignore
|
||||
</div>
|
||||
@@ -150,7 +149,6 @@ exports[`<MjolnirUserSettingsTab /> renders correctly when user has no ignored u
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
Subscribe
|
||||
</div>
|
||||
|
||||
17
yarn.lock
17
yarn.lock
@@ -2005,10 +2005,10 @@
|
||||
emojibase "^15.3.1"
|
||||
emojibase-data "^15.3.1"
|
||||
|
||||
"@matrix-org/matrix-sdk-crypto-wasm@^9.0.0":
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-9.1.0.tgz#f889653eb4fafaad2a963654d586bd34de62acd5"
|
||||
integrity sha512-CtPoNcoRW6ehwxpRQAksG3tR+NJ7k4DV02nMFYTDwQtie1V4R8OTY77BjEIs97NOblhtS26jU8m1lWsOBEz0Og==
|
||||
"@matrix-org/matrix-sdk-crypto-wasm@^11.1.0":
|
||||
version "11.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-11.1.0.tgz#289ac0b13961f51329bbecaf6bf14145ab349967"
|
||||
integrity sha512-JPuO9RCVDklDjbFzMvZfQb7PuiFkLY72bniRSu81lRzkkrcbZtmKqBFMm9H4f2FSz+tHVkDnmsvn12I4sdJJ5A==
|
||||
|
||||
"@matrix-org/olm@3.2.15":
|
||||
version "3.2.15"
|
||||
@@ -8237,12 +8237,13 @@ matrix-events-sdk@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
||||
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
||||
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||
version "34.13.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/c4ea57d42dcf8bd04c40feaa2c686487dbcab338"
|
||||
matrix-js-sdk@35.1.0:
|
||||
version "35.1.0"
|
||||
resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-35.1.0.tgz#3dad40bd6fa1ae97a80022718f51941aa6f76af2"
|
||||
integrity sha512-uADi0Uj3uASPe20DuAKEc17/3fywCkla6cHqq4vTd+FgxDWo8nngozl8ykINIU+Y3VimcrxeciGvUjTyHcA9pg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0"
|
||||
"@matrix-org/matrix-sdk-crypto-wasm" "^11.1.0"
|
||||
"@matrix-org/olm" "3.2.15"
|
||||
another-json "^0.2.0"
|
||||
bs58 "^6.0.0"
|
||||
|
||||
Reference in New Issue
Block a user