Compare commits

...

16 Commits

Author SHA1 Message Date
Half-Shot
d733f65cf3 Add FOSDEM module changes. 2025-01-30 17:24:53 +00:00
RiotRobot
07f1680ba0 v1.11.89 2024-12-18 17:18:31 +00:00
ElementRobot
3fbc9e6de6 Fix url preview display (#28765) (#28766)
(cherry picked from commit 117bee787f)

Co-authored-by: Florian Duros <florianduros@element.io>
2024-12-18 17:09:08 +00:00
RiotRobot
e7d9df24e2 Upgrade dependency to matrix-js-sdk@35.1.0 2024-12-18 14:12:15 +00:00
RiotRobot
ad77f7943b v1.11.88 2024-12-17 13:32:35 +00:00
RiotRobot
89d7dca464 Upgrade dependency to matrix-js-sdk@35.0.0 2024-12-17 13:28:57 +00:00
RiotRobot
af3040fb62 v1.11.88-rc.0 2024-12-10 15:54:01 +00:00
RiotRobot
b6ba3335ec Upgrade dependency to matrix-js-sdk@35.0.0-rc.0 2024-12-10 15:50:25 +00:00
Hugh Nimmo-Smith
6b7c94905f Allow trusted Element Call widget to send and receive media encryption key to-device messages (#28316) 2024-12-10 12:05:30 +00:00
ElementRobot
a4e8bb3f9a [create-pull-request] automated change (#28696)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-12-10 06:17:13 +00:00
Hubert Chathi
2b4000d47f Add delay in test to allow Alice to fetch Bob's device keys (#28668)
* add delay in test to allow Alice to fetch Bob's device keys

* wait until we see bob's device, rather than hard-coding a timeout

* Fix comment

Co-authored-by: Florian Duros <florianduros@element.io>

* fix lint

---------

Co-authored-by: Florian Duros <florianduros@element.io>
2024-12-09 21:02:29 +00:00
Michael Telatynski
01304439ee Make tsc faster again (#28678)
* Stash initial work to bring TSC from over 6 mins to under 1 minute

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Stabilise types

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix incorrect props to AccessibleButton

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Swap AccessibleButton element types to match the props they provide

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Changed my mind, remove spurious previously ignored props

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-12-06 17:49:32 +00:00
David Baker
c659afa8db Rename CreateCrossSigningDialog to InitialCryptoSetupDialog (#28658)
* Rename CreateCrossSigningDialog to InitialCryptoSetup

because it will soon encompass things other than just creating cross
signing.

* Fix name & tests

* Fix import

* Remove code creating key backup

Because this was split out from my key backup by default PR

* Fix comment

* Convert to named export
2024-12-06 10:26:26 +00:00
ElementRobot
9cc5564d50 [create-pull-request] automated change (#28670)
Co-authored-by: t3chguy <t3chguy@users.noreply.github.com>
2024-12-06 09:38:58 +00:00
Michael Telatynski
549300726f Update CODEOWNERS 2024-12-06 09:18:33 +00:00
ElementRobot
319dab5920 [create-pull-request] automated change (#28669)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2024-12-06 06:17:19 +00:00
44 changed files with 318 additions and 195 deletions

1
.github/CODEOWNERS vendored
View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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}

View File

@@ -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}

View File

@@ -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);
}}

View File

@@ -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);

View File

@@ -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

View File

@@ -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}

View File

@@ -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">

View File

@@ -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);

View File

@@ -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"
>

View File

@@ -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;

View File

@@ -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"

View File

@@ -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}
/>
);
};

View File

@@ -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))}
/>
),

View File

@@ -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 were creating that the element
* actually supports.
*
* e.g., if element is set to "a", well support href and target, if its set to "input", we support type.
*
* To remain compatible with existing code, well 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;

View File

@@ -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>

View File

@@ -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>;
}

View File

@@ -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"

View File

@@ -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" />

View File

@@ -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"

View File

@@ -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();
}
}

View File

@@ -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()}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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()}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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", {

View File

@@ -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",

View File

@@ -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.",

View File

@@ -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",

View File

@@ -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.`,
);
}
}
}
}

View File

@@ -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(

View File

@@ -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}

View File

@@ -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>

View File

@@ -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();
});
});
});

View File

@@ -35,7 +35,7 @@ describe("SetIntegrationManager", () => {
deleteThreePid: jest.fn(),
});
let stores: SdkContextClass;
let stores!: SdkContextClass;
const getComponent = () => (
<MatrixClientContext.Provider value={mockClient}>

View File

@@ -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>

View File

@@ -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"