mirror of
https://github.com/element-hq/element-web.git
synced 2025-12-09 01:30:57 +00:00
Compare commits
11 Commits
dbkr/secur
...
t3chguy/pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc03ba64de | ||
|
|
859ba0e02c | ||
|
|
56eafc908e | ||
|
|
1644169ff3 | ||
|
|
cf895b4296 | ||
|
|
e9d4f39e9d | ||
|
|
7c0ec21365 | ||
|
|
72df9c9076 | ||
|
|
e42ee727b4 | ||
|
|
7d30413178 | ||
|
|
8884e77ce3 |
1
.github/workflows/deploy.yml
vendored
1
.github/workflows/deploy.yml
vendored
@@ -96,3 +96,4 @@ jobs:
|
||||
projectName: ${{ env.SITE == 'staging.element.io' && 'element-web-staging' || 'element-web' }}
|
||||
directory: _deploy
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: main
|
||||
|
||||
@@ -13,6 +13,14 @@ import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { Layout } from "../../../src/settings/enums/Layout";
|
||||
import { ElementAppPage } from "../../pages/ElementAppPage";
|
||||
|
||||
// Find and click "Reply" button
|
||||
const clickButtonReply = async (tile: Locator) => {
|
||||
await expect(async () => {
|
||||
await tile.hover();
|
||||
await tile.getByRole("button", { name: "Reply", exact: true }).click();
|
||||
}).toPass();
|
||||
};
|
||||
|
||||
test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
test.use({
|
||||
displayName: "Hanako",
|
||||
@@ -222,8 +230,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
|
||||
// Find and click "Reply" button on MessageActionBar
|
||||
const tile = page.locator(".mx_EventTile_last");
|
||||
await tile.hover();
|
||||
await tile.getByRole("button", { name: "Reply", exact: true }).click();
|
||||
await clickButtonReply(tile);
|
||||
|
||||
// Reply to the player with another audio file
|
||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||
@@ -251,18 +258,12 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
|
||||
const tile = page.locator(".mx_EventTile_last");
|
||||
|
||||
// Find and click "Reply" button
|
||||
const clickButtonReply = async () => {
|
||||
await tile.hover();
|
||||
await tile.getByRole("button", { name: "Reply", exact: true }).click();
|
||||
};
|
||||
|
||||
await uploadFile(page, "playwright/sample-files/upload-first.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
||||
|
||||
await clickButtonReply();
|
||||
await clickButtonReply(tile);
|
||||
|
||||
// Reply to the player with another audio file
|
||||
await uploadFile(page, "playwright/sample-files/upload-second.ogg");
|
||||
@@ -270,7 +271,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
// Assert that the audio player is rendered
|
||||
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
||||
|
||||
await clickButtonReply();
|
||||
await clickButtonReply(tile);
|
||||
|
||||
// Reply to the player with yet another audio file to create a reply chain
|
||||
await uploadFile(page, "playwright/sample-files/upload-third.ogg");
|
||||
|
||||
@@ -81,6 +81,7 @@ test.describe("Create Knock Room", () => {
|
||||
|
||||
const spotlightDialog = await app.openSpotlight();
|
||||
await spotlightDialog.filter(Filter.PublicRooms);
|
||||
await spotlightDialog.search("Cyber");
|
||||
await expect(spotlightDialog.results.nth(0)).toContainText("Cybersecurity");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -284,6 +284,7 @@ test.describe("Knock Into Room", () => {
|
||||
|
||||
const spotlightDialog = await app.openSpotlight();
|
||||
await spotlightDialog.filter(Filter.PublicRooms);
|
||||
await spotlightDialog.search("Cyber");
|
||||
await expect(spotlightDialog.results.nth(0)).toContainText("Cybersecurity");
|
||||
await spotlightDialog.results.nth(0).click();
|
||||
|
||||
|
||||
@@ -58,6 +58,16 @@ async function editMessage(page: Page, message: Locator, newMsg: string): Promis
|
||||
await editComposer.press("Enter");
|
||||
}
|
||||
|
||||
const screenshotOptions = (page?: Page) => ({
|
||||
mask: page ? [page.locator(".mx_MessageTimestamp")] : undefined,
|
||||
// Hide the jump to bottom button in the timeline to avoid flakiness
|
||||
css: `
|
||||
.mx_JumpToBottomButton {
|
||||
display: none !important;
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
test.describe("Message rendering", () => {
|
||||
[
|
||||
{ direction: "ltr", displayName: "Quentin" },
|
||||
@@ -79,9 +89,10 @@ test.describe("Message rendering", () => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
|
||||
const msgTile = await sendMessage(page, "Hello, world!");
|
||||
await expect(msgTile).toMatchScreenshot(`basic-message-ltr-${direction}displayname.png`, {
|
||||
mask: [page.locator(".mx_MessageTimestamp")],
|
||||
});
|
||||
await expect(msgTile).toMatchScreenshot(
|
||||
`basic-message-ltr-${direction}displayname.png`,
|
||||
screenshotOptions(page),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -89,14 +100,17 @@ test.describe("Message rendering", () => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
|
||||
const msgTile = await sendMessage(page, "/me lays an egg");
|
||||
await expect(msgTile).toMatchScreenshot(`emote-ltr-${direction}displayname.png`);
|
||||
await expect(msgTile).toMatchScreenshot(`emote-ltr-${direction}displayname.png`, screenshotOptions());
|
||||
});
|
||||
|
||||
test("should render an LTR rich text emote", async ({ page, user, app, room }) => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
|
||||
const msgTile = await sendMessage(page, "/me lays a *free range* egg");
|
||||
await expect(msgTile).toMatchScreenshot(`emote-rich-ltr-${direction}displayname.png`);
|
||||
await expect(msgTile).toMatchScreenshot(
|
||||
`emote-rich-ltr-${direction}displayname.png`,
|
||||
screenshotOptions(),
|
||||
);
|
||||
});
|
||||
|
||||
test("should render an edited LTR message", async ({ page, user, app, room }) => {
|
||||
@@ -106,9 +120,10 @@ test.describe("Message rendering", () => {
|
||||
|
||||
await editMessage(page, msgTile, "Hello, universe!");
|
||||
|
||||
await expect(msgTile).toMatchScreenshot(`edited-message-ltr-${direction}displayname.png`, {
|
||||
mask: [page.locator(".mx_MessageTimestamp")],
|
||||
});
|
||||
await expect(msgTile).toMatchScreenshot(
|
||||
`edited-message-ltr-${direction}displayname.png`,
|
||||
screenshotOptions(page),
|
||||
);
|
||||
});
|
||||
|
||||
test("should render a reply of a LTR message", async ({ page, user, app, room }) => {
|
||||
@@ -122,32 +137,37 @@ test.describe("Message rendering", () => {
|
||||
]);
|
||||
|
||||
await replyMessage(page, msgTile, "response to multiline message");
|
||||
await expect(msgTile).toMatchScreenshot(`reply-message-ltr-${direction}displayname.png`, {
|
||||
mask: [page.locator(".mx_MessageTimestamp")],
|
||||
});
|
||||
await expect(msgTile).toMatchScreenshot(
|
||||
`reply-message-ltr-${direction}displayname.png`,
|
||||
screenshotOptions(page),
|
||||
);
|
||||
});
|
||||
|
||||
test("should render a basic RTL text message", async ({ page, user, app, room }) => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
|
||||
const msgTile = await sendMessage(page, "مرحبا بالعالم!");
|
||||
await expect(msgTile).toMatchScreenshot(`basic-message-rtl-${direction}displayname.png`, {
|
||||
mask: [page.locator(".mx_MessageTimestamp")],
|
||||
});
|
||||
await expect(msgTile).toMatchScreenshot(
|
||||
`basic-message-rtl-${direction}displayname.png`,
|
||||
screenshotOptions(page),
|
||||
);
|
||||
});
|
||||
|
||||
test("should render an RTL emote", async ({ page, user, app, room }) => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
|
||||
const msgTile = await sendMessage(page, "/me يضع بيضة");
|
||||
await expect(msgTile).toMatchScreenshot(`emote-rtl-${direction}displayname.png`);
|
||||
await expect(msgTile).toMatchScreenshot(`emote-rtl-${direction}displayname.png`, screenshotOptions());
|
||||
});
|
||||
|
||||
test("should render a richtext RTL emote", async ({ page, user, app, room }) => {
|
||||
await page.goto(`#/room/${room.roomId}`);
|
||||
|
||||
const msgTile = await sendMessage(page, "/me أضع بيضة *حرة النطاق*");
|
||||
await expect(msgTile).toMatchScreenshot(`emote-rich-rtl-${direction}displayname.png`);
|
||||
await expect(msgTile).toMatchScreenshot(
|
||||
`emote-rich-rtl-${direction}displayname.png`,
|
||||
screenshotOptions(),
|
||||
);
|
||||
});
|
||||
|
||||
test("should render an edited RTL message", async ({ page, user, app, room }) => {
|
||||
@@ -157,9 +177,10 @@ test.describe("Message rendering", () => {
|
||||
|
||||
await editMessage(page, msgTile, "مرحبا بالكون!");
|
||||
|
||||
await expect(msgTile).toMatchScreenshot(`edited-message-rtl-${direction}displayname.png`, {
|
||||
mask: [page.locator(".mx_MessageTimestamp")],
|
||||
});
|
||||
await expect(msgTile).toMatchScreenshot(
|
||||
`edited-message-rtl-${direction}displayname.png`,
|
||||
screenshotOptions(page),
|
||||
);
|
||||
});
|
||||
|
||||
test("should render a reply of a RTL message", async ({ page, user, app, room }) => {
|
||||
@@ -173,9 +194,10 @@ test.describe("Message rendering", () => {
|
||||
]);
|
||||
|
||||
await replyMessage(page, msgTile, "مرحبا بالعالم!");
|
||||
await expect(msgTile).toMatchScreenshot(`reply-message-trl-${direction}displayname.png`, {
|
||||
mask: [page.locator(".mx_MessageTimestamp")],
|
||||
});
|
||||
await expect(msgTile).toMatchScreenshot(
|
||||
`reply-message-trl-${direction}displayname.png`,
|
||||
screenshotOptions(page),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,10 +35,10 @@ test.describe("Pinned messages", () => {
|
||||
mask: [tile.locator(".mx_MessageTimestamp")],
|
||||
// Hide the jump to bottom button in the timeline to avoid flakiness
|
||||
css: `
|
||||
.mx_JumpToBottomButton {
|
||||
display: none !important;
|
||||
}
|
||||
`,
|
||||
.mx_JumpToBottomButton {
|
||||
display: none !important;
|
||||
}
|
||||
`,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -89,8 +89,7 @@ class Helpers {
|
||||
await expect(dialog.getByText(title, { exact: true })).toBeVisible();
|
||||
await expect(dialog).toMatchScreenshot(screenshot);
|
||||
|
||||
const handle = await this.page.evaluateHandle(() => navigator.clipboard.readText());
|
||||
const clipboardContent = await handle.jsonValue();
|
||||
const clipboardContent = await this.app.getClipboard();
|
||||
await dialog.getByRole("textbox").fill(clipboardContent);
|
||||
await dialog.getByRole("button", { name: confirmButtonLabel }).click();
|
||||
await expect(dialog).toMatchScreenshot("default-recovery.png");
|
||||
|
||||
@@ -53,7 +53,7 @@ test.describe("Recovery section in Encryption tab", () => {
|
||||
|
||||
test(
|
||||
"should change the recovery key",
|
||||
{ tag: "@screenshot" },
|
||||
{ tag: ["@screenshot", "@no-webkit"] },
|
||||
async ({ page, app, homeserver, credentials, util, context }) => {
|
||||
await verifySession(app, "new passphrase");
|
||||
const dialog = await util.openEncryptionTab();
|
||||
@@ -81,7 +81,7 @@ test.describe("Recovery section in Encryption tab", () => {
|
||||
},
|
||||
);
|
||||
|
||||
test("should setup the recovery key", { tag: "@screenshot" }, async ({ page, app, util }) => {
|
||||
test("should setup the recovery key", { tag: ["@screenshot", "@no-webkit"] }, async ({ page, app, util }) => {
|
||||
await verifySession(app, "new passphrase");
|
||||
await util.removeSecretStorageDefaultKeyId();
|
||||
|
||||
|
||||
@@ -69,11 +69,6 @@ const test = base.extend<{
|
||||
});
|
||||
|
||||
test.describe("Sliding Sync", () => {
|
||||
test.skip(
|
||||
({ homeserverType }) => homeserverType === "pinecone",
|
||||
"due to a bug in Pinecone https://github.com/element-hq/dendrite/issues/3490",
|
||||
);
|
||||
|
||||
const checkOrder = async (wantOrder: string[], page: Page) => {
|
||||
await expect(page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomTile_title")).toHaveText(wantOrder);
|
||||
};
|
||||
|
||||
@@ -84,7 +84,7 @@ test.describe("Spaces", () => {
|
||||
|
||||
// Copy matrix.to link
|
||||
await page.getByRole("button", { name: "Share invite link" }).click();
|
||||
expect(await app.getClipboardText()).toEqual(`https://matrix.to/#/#lets-have-a-riot:${user.homeServer}`);
|
||||
expect(await app.getClipboard()).toEqual(`https://matrix.to/#/#lets-have-a-riot:${user.homeServer}`);
|
||||
|
||||
// Go to space home
|
||||
await page.getByRole("button", { name: "Go to my first room" }).click();
|
||||
@@ -177,7 +177,7 @@ test.describe("Spaces", () => {
|
||||
const shareDialog = page.locator(".mx_SpacePublicShare");
|
||||
// Copy link first
|
||||
await shareDialog.getByRole("button", { name: "Share invite link" }).click();
|
||||
expect(await app.getClipboardText()).toEqual(`https://matrix.to/#/#space:${user.homeServer}`);
|
||||
expect(await app.getClipboard()).toEqual(`https://matrix.to/#/#space:${user.homeServer}`);
|
||||
// Start Matrix invite flow
|
||||
await shareDialog.getByRole("button", { name: "Invite people" }).click();
|
||||
|
||||
|
||||
@@ -38,11 +38,13 @@ export const test = base.extend<{
|
||||
room1Name: "Room 1",
|
||||
room1: async ({ room1Name: name, app, user, bot }, use) => {
|
||||
const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] });
|
||||
await bot.awaitRoomMembership(roomId);
|
||||
await use({ name, roomId });
|
||||
},
|
||||
room2Name: "Room 2",
|
||||
room2: async ({ room2Name: name, app, user, bot }, use) => {
|
||||
const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] });
|
||||
await bot.awaitRoomMembership(roomId);
|
||||
await use({ name, roomId });
|
||||
},
|
||||
msg: async ({ page, app, util }, use) => {
|
||||
|
||||
@@ -1195,6 +1195,7 @@ test.describe("Timeline", () => {
|
||||
});
|
||||
|
||||
await sendImage(app.client, room.roomId, NEW_AVATAR);
|
||||
await app.timeline.scrollToBottom();
|
||||
await expect(page.locator(".mx_MImageBody").first()).toBeVisible();
|
||||
|
||||
// Exclude timestamp and read marker from snapshot
|
||||
|
||||
@@ -24,18 +24,40 @@ type PaginationLinks = {
|
||||
first?: string;
|
||||
};
|
||||
|
||||
// We see quite a few test flakes which are caused by the app exploding
|
||||
// so we have some magic strings we check the logs for to better track the flake with its cause
|
||||
const SPECIAL_CASES = {
|
||||
"ChunkLoadError": "ChunkLoadError",
|
||||
"Unreachable code should not be executed": "Rust crypto panic",
|
||||
"Out of bounds memory access": "Rust crypto memory error",
|
||||
};
|
||||
|
||||
class FlakyReporter implements Reporter {
|
||||
private flakes = new Map<string, TestCase[]>();
|
||||
|
||||
public onTestEnd(test: TestCase): void {
|
||||
// Ignores flakes on Dendrite and Pinecone as they have their own flakes we do not track
|
||||
if (["Dendrite", "Pinecone"].includes(test.parent.project()?.name)) return;
|
||||
const title = `${test.location.file.split("playwright/e2e/")[1]}: ${test.title}`;
|
||||
let failures = [`${test.location.file.split("playwright/e2e/")[1]}: ${test.title}`];
|
||||
if (test.outcome() === "flaky") {
|
||||
if (!this.flakes.has(title)) {
|
||||
this.flakes.set(title, []);
|
||||
const timedOutRuns = test.results.filter((result) => result.status === "timedOut");
|
||||
const pageLogs = timedOutRuns.flatMap((result) =>
|
||||
result.attachments.filter((attachment) => attachment.name.startsWith("page-")),
|
||||
);
|
||||
// If a test failed due to a systemic fault then the test is not flaky, the app is, record it as such.
|
||||
const specialCases = Object.keys(SPECIAL_CASES).filter((log) =>
|
||||
pageLogs.some((attachment) => attachment.name.startsWith("page-") && attachment.body.includes(log)),
|
||||
);
|
||||
if (specialCases.length > 0) {
|
||||
failures = specialCases.map((specialCase) => SPECIAL_CASES[specialCase]);
|
||||
}
|
||||
|
||||
for (const title of failures) {
|
||||
if (!this.flakes.has(title)) {
|
||||
this.flakes.set(title, []);
|
||||
}
|
||||
this.flakes.get(title).push(test);
|
||||
}
|
||||
this.flakes.get(title).push(test);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,10 +158,6 @@ export class ElementAppPage {
|
||||
return button.click();
|
||||
}
|
||||
|
||||
public async getClipboardText(): Promise<string> {
|
||||
return this.page.evaluate("navigator.clipboard.readText()");
|
||||
}
|
||||
|
||||
public async openSpotlight(): Promise<Spotlight> {
|
||||
const spotlight = new Spotlight(this.page);
|
||||
await spotlight.open();
|
||||
|
||||
@@ -15,7 +15,6 @@ import type {
|
||||
ICreateRoomOpts,
|
||||
ISendEventResponse,
|
||||
MatrixClient,
|
||||
Room,
|
||||
MatrixEvent,
|
||||
ReceiptType,
|
||||
IRoomDirectoryOptions,
|
||||
@@ -178,21 +177,12 @@ export class Client {
|
||||
*/
|
||||
public async createRoom(options: ICreateRoomOpts): Promise<string> {
|
||||
const client = await this.prepareClient();
|
||||
return await client.evaluate(async (cli, options) => {
|
||||
const roomId = await client.evaluate(async (cli, options) => {
|
||||
const { room_id: roomId } = await cli.createRoom(options);
|
||||
if (!cli.getRoom(roomId)) {
|
||||
await new Promise<void>((resolve) => {
|
||||
const onRoom = (room: Room) => {
|
||||
if (room.roomId === roomId) {
|
||||
cli.off(window.matrixcs.ClientEvent.Room, onRoom);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
cli.on(window.matrixcs.ClientEvent.Room, onRoom);
|
||||
});
|
||||
}
|
||||
return roomId;
|
||||
}, options);
|
||||
await this.awaitRoomMembership(roomId);
|
||||
return roomId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -155,9 +155,13 @@ export const test = base.extend<TestFixtures, Services & Options>({
|
||||
{ scope: "worker" },
|
||||
],
|
||||
|
||||
context: async ({ homeserverType, synapseConfig, logger, context, request, homeserver }, use, testInfo) => {
|
||||
context: async (
|
||||
{ homeserverType, synapseConfig, logger, context, request, _homeserver, homeserver },
|
||||
use,
|
||||
testInfo,
|
||||
) => {
|
||||
testInfo.skip(
|
||||
!(homeserver instanceof SynapseContainer) && Object.keys(synapseConfig).length > 0,
|
||||
!(_homeserver instanceof SynapseContainer) && Object.keys(synapseConfig).length > 0,
|
||||
`Test specifies Synapse config options so is unsupported with ${homeserverType}`,
|
||||
);
|
||||
homeserver.setRequest(request);
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@@ -19,7 +19,7 @@ import { HomeserverContainer, StartedHomeserverContainer } from "./HomeserverCon
|
||||
import { StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
|
||||
import { Api, ClientServerApi, Verb } from "../plugins/utils/api.ts";
|
||||
|
||||
const TAG = "develop@sha256:436278578c6b396d3a581f6af020edaff37dd7c3d26d20362de9e05e4a70cee8";
|
||||
const TAG = "develop@sha256:5d62b61c4373eaca25df6c6bb99fc1be92f8f40b8abebd8897bf5b2af9eb137a";
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
server_name: "localhost",
|
||||
|
||||
@@ -283,6 +283,7 @@
|
||||
@import "./views/rooms/_EventTile.pcss";
|
||||
@import "./views/rooms/_HistoryTile.pcss";
|
||||
@import "./views/rooms/_IRCLayout.pcss";
|
||||
@import "./views/rooms/_InvitedIconView.pcss";
|
||||
@import "./views/rooms/_JumpToBottomButton.pcss";
|
||||
@import "./views/rooms/_LinkPreviewGroup.pcss";
|
||||
@import "./views/rooms/_LinkPreviewWidget.pcss";
|
||||
|
||||
10
res/css/views/rooms/_InvitedIconView.pcss
Normal file
10
res/css/views/rooms/_InvitedIconView.pcss
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_InvitedIconView {
|
||||
color: var(--cpd-color-icon-tertiary);
|
||||
}
|
||||
@@ -14,4 +14,10 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_MemberListView_container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mx_MemberListView_separator {
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-top: 2px solid var(--cpd-color-bg-subtle-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,10 @@ Please see LICENSE files in the repository root for full details.
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mx_MemberTileView_user_label {
|
||||
.mx_MemberTileView_userLabel {
|
||||
font: var(--cpd-font-body-sm-regular);
|
||||
font-size: 13px;
|
||||
color: var(--cpd-color-text-secondary);
|
||||
}
|
||||
|
||||
.mx_MemberTileView_avatar {
|
||||
@@ -41,18 +42,4 @@ Please see LICENSE files in the repository root for full details.
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.mx_E2EIconView {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mx_E2EIconView_warning {
|
||||
color: var(--cpd-color-icon-critical-primary);
|
||||
}
|
||||
|
||||
.mx_E2EIconView_verified {
|
||||
color: var(--cpd-color-icon-success-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { StrictMode } from "react";
|
||||
import { createRoot, Root } from "react-dom/client";
|
||||
import classNames from "classnames";
|
||||
import { IDeferred, defer } from "matrix-js-sdk/src/utils";
|
||||
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
|
||||
import { Glass, TooltipProvider } from "@vector-im/compound-web";
|
||||
|
||||
@@ -45,7 +44,7 @@ export interface IModal<C extends ComponentType> {
|
||||
onFinished: ComponentProps<C>["onFinished"];
|
||||
close(...args: Parameters<ComponentProps<C>["onFinished"]>): void;
|
||||
hidden?: boolean;
|
||||
deferred?: IDeferred<Parameters<ComponentProps<C>["onFinished"]>>;
|
||||
deferred?: PromiseWithResolvers<Parameters<ComponentProps<C>["onFinished"]>>;
|
||||
}
|
||||
|
||||
export interface IHandle<C extends ComponentType> {
|
||||
@@ -214,7 +213,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
modal: IModal<C>,
|
||||
props?: ComponentProps<C>,
|
||||
): [IHandle<C>["close"], IHandle<C>["finished"]] {
|
||||
modal.deferred = defer<Parameters<ComponentProps<C>["onFinished"]>>();
|
||||
modal.deferred = Promise.withResolvers<Parameters<ComponentProps<C>["onFinished"]>>();
|
||||
return [
|
||||
async (...args: Parameters<ComponentProps<C>["onFinished"]>): Promise<void> => {
|
||||
if (modal.beforeClosePromise) {
|
||||
|
||||
@@ -46,7 +46,7 @@ import {
|
||||
SlidingSync,
|
||||
} from "matrix-js-sdk/src/sliding-sync";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { defer, sleep } from "matrix-js-sdk/src/utils";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
import SlidingSyncController from "./settings/controllers/SlidingSyncController";
|
||||
@@ -110,7 +110,7 @@ export class SlidingSyncManager {
|
||||
public slidingSync?: SlidingSync;
|
||||
private client?: MatrixClient;
|
||||
|
||||
private configureDefer = defer<void>();
|
||||
private configureDefer = Promise.withResolvers<void>();
|
||||
|
||||
public static get instance(): SlidingSyncManager {
|
||||
return SlidingSyncManager.internalInstance;
|
||||
|
||||
@@ -6,14 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { WorkerPayload } from "./workers/worker";
|
||||
|
||||
export class WorkerManager<Request extends {}, Response> {
|
||||
private readonly worker: Worker;
|
||||
private seq = 0;
|
||||
private pendingDeferredMap = new Map<number, IDeferred<Response>>();
|
||||
private pendingDeferredMap = new Map<number, PromiseWithResolvers<Response>>();
|
||||
|
||||
public constructor(worker: Worker) {
|
||||
this.worker = worker;
|
||||
@@ -30,7 +28,7 @@ export class WorkerManager<Request extends {}, Response> {
|
||||
|
||||
public call(request: Request): Promise<Response> {
|
||||
const seq = this.seq++;
|
||||
const deferred = defer<Response>();
|
||||
const deferred = Promise.withResolvers<Response>();
|
||||
this.pendingDeferredMap.set(seq, deferred);
|
||||
this.worker.postMessage({ seq, ...request });
|
||||
return deferred.promise;
|
||||
|
||||
@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
import EventEmitter from "events";
|
||||
import { SimpleObservable } from "matrix-widget-api";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { UPDATE_EVENT } from "../stores/AsyncStore";
|
||||
import { arrayFastResample } from "../utils/arrays";
|
||||
@@ -158,7 +157,7 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
|
||||
// 5mb
|
||||
logger.log("Audio file too large: processing through <audio /> element");
|
||||
this.element = document.createElement("AUDIO") as HTMLAudioElement;
|
||||
const deferred = defer<unknown>();
|
||||
const deferred = Promise.withResolvers<unknown>();
|
||||
this.element.onloadeddata = deferred.resolve;
|
||||
this.element.onerror = deferred.reject;
|
||||
this.element.src = URL.createObjectURL(new Blob([this.buf]));
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
SyncStateData,
|
||||
TimelineEvents,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils";
|
||||
import { QueryDict } from "matrix-js-sdk/src/utils";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { throttle } from "lodash";
|
||||
import { CryptoEvent, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
||||
@@ -217,7 +217,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
};
|
||||
|
||||
private firstSyncComplete = false;
|
||||
private firstSyncPromise: IDeferred<void>;
|
||||
private firstSyncPromise: PromiseWithResolvers<void>;
|
||||
|
||||
private screenAfterLogin?: IScreen;
|
||||
private tokenLogin?: boolean;
|
||||
@@ -255,7 +255,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
|
||||
// Used by _viewRoom before getting state from sync
|
||||
this.firstSyncComplete = false;
|
||||
this.firstSyncPromise = defer();
|
||||
this.firstSyncPromise = Promise.withResolvers();
|
||||
|
||||
if (this.props.config.sync_timeline_limit) {
|
||||
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
|
||||
@@ -1503,7 +1503,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
// since we're about to start the client and therefore about
|
||||
// to do the first sync
|
||||
this.firstSyncComplete = false;
|
||||
this.firstSyncPromise = defer();
|
||||
this.firstSyncPromise = Promise.withResolvers();
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
|
||||
// Allow the JS SDK to reap timeline events. This reduces the amount of
|
||||
|
||||
@@ -99,8 +99,12 @@ export function sdkRoomMemberToRoomMember(member: SdkRoomMember): Member {
|
||||
};
|
||||
}
|
||||
|
||||
export const SEPARATOR = "SEPARATOR";
|
||||
export type MemberWithSeparator = Member | typeof SEPARATOR;
|
||||
|
||||
export interface MemberListViewState {
|
||||
members: Member[];
|
||||
members: MemberWithSeparator[];
|
||||
memberCount: number;
|
||||
search: (searchQuery: string) => void;
|
||||
isPresenceEnabled: boolean;
|
||||
shouldShowInvite: boolean;
|
||||
@@ -118,10 +122,16 @@ export function useMemberListViewModel(roomId: string): MemberListViewState {
|
||||
}
|
||||
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const [memberMap, setMemberMap] = useState<Map<string, Member>>(new Map());
|
||||
const [memberMap, setMemberMap] = useState<Map<string, MemberWithSeparator>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
// This is the last known total number of members in this room.
|
||||
const [totalMemberCount, setTotalMemberCount] = useState(0);
|
||||
/**
|
||||
* This is the current number of members in the list.
|
||||
* This number will be less than the total number of members
|
||||
* in the room when the search functionality is used.
|
||||
*/
|
||||
const [memberCount, setMemberCount] = useState(0);
|
||||
|
||||
const loadMembers = useMemo(
|
||||
() =>
|
||||
@@ -131,24 +141,34 @@ export function useMemberListViewModel(roomId: string): MemberListViewState {
|
||||
roomId,
|
||||
searchQuery,
|
||||
);
|
||||
const newMemberMap = new Map<string, Member>();
|
||||
// First add the invited room members
|
||||
for (const member of invitedSdk) {
|
||||
const roomMember = sdkRoomMemberToRoomMember(member);
|
||||
newMemberMap.set(member.userId, roomMember);
|
||||
}
|
||||
// Then add the third party invites
|
||||
const threePidInvited = getPending3PidInvites(room, searchQuery);
|
||||
for (const invited of threePidInvited) {
|
||||
const key = invited.threePidInvite!.event.getContent().display_name;
|
||||
newMemberMap.set(key, invited);
|
||||
}
|
||||
// Finally add the joined room members
|
||||
|
||||
const newMemberMap = new Map<string, MemberWithSeparator>();
|
||||
|
||||
// First add the joined room members
|
||||
for (const member of joinedSdk) {
|
||||
const roomMember = sdkRoomMemberToRoomMember(member);
|
||||
newMemberMap.set(member.userId, roomMember);
|
||||
}
|
||||
|
||||
// Then a separator if needed
|
||||
if (joinedSdk.length > 0 && (invitedSdk.length > 0 || threePidInvited.length > 0))
|
||||
newMemberMap.set(SEPARATOR, SEPARATOR);
|
||||
|
||||
// Then add the invited room members
|
||||
for (const member of invitedSdk) {
|
||||
const roomMember = sdkRoomMemberToRoomMember(member);
|
||||
newMemberMap.set(member.userId, roomMember);
|
||||
}
|
||||
|
||||
// Finally add the third party invites
|
||||
for (const invited of threePidInvited) {
|
||||
const key = invited.threePidInvite!.event.getContent().display_name;
|
||||
newMemberMap.set(key, invited);
|
||||
}
|
||||
|
||||
setMemberMap(newMemberMap);
|
||||
setMemberCount(joinedSdk.length + invitedSdk.length + threePidInvited.length);
|
||||
if (!searchQuery) {
|
||||
/**
|
||||
* Since searching for members only gives you the relevant
|
||||
@@ -241,6 +261,7 @@ export function useMemberListViewModel(roomId: string): MemberListViewState {
|
||||
|
||||
return {
|
||||
members: Array.from(memberMap.values()),
|
||||
memberCount,
|
||||
search: loadMembers,
|
||||
shouldShowInvite,
|
||||
isPresenceEnabled,
|
||||
|
||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import dis from "../../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../../dispatcher/actions";
|
||||
import { ThreePIDInvite } from "../../../../models/rooms/ThreePIDInvite";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
|
||||
interface ThreePidTileViewModelProps {
|
||||
threePidInvite: ThreePIDInvite;
|
||||
@@ -16,6 +17,7 @@ interface ThreePidTileViewModelProps {
|
||||
export interface ThreePidTileViewState {
|
||||
name: string;
|
||||
onClick: () => void;
|
||||
userLabel?: string;
|
||||
}
|
||||
|
||||
export function useThreePidTileViewModel(props: ThreePidTileViewModelProps): ThreePidTileViewState {
|
||||
@@ -28,8 +30,11 @@ export function useThreePidTileViewModel(props: ThreePidTileViewModelProps): Thr
|
||||
});
|
||||
};
|
||||
|
||||
const userLabel = `(${_t("member_list|invited_label")})`;
|
||||
|
||||
return {
|
||||
name,
|
||||
onClick,
|
||||
userLabel,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
import { MatrixEvent, EventType, RelationType, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
@@ -58,7 +57,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
|
||||
const eventId = this.props.mxEvent.getId()!;
|
||||
const client = MatrixClientPeg.safeGet();
|
||||
|
||||
const { resolve, reject, promise } = defer<boolean>();
|
||||
const { resolve, reject, promise } = Promise.withResolvers<boolean>();
|
||||
let result: Awaited<ReturnType<MatrixClient["relations"]>>;
|
||||
|
||||
try {
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
|
||||
import ToggleSwitch from "./ToggleSwitch";
|
||||
import { Caption } from "../typography/Caption";
|
||||
@@ -36,7 +36,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
export default class LabelledToggleSwitch extends React.PureComponent<IProps> {
|
||||
private readonly id = `mx_LabelledToggleSwitch_${randomString(12)}`;
|
||||
private readonly id = `mx_LabelledToggleSwitch_${secureRandomString(12)}`;
|
||||
|
||||
public render(): React.ReactNode {
|
||||
// This is a minimal version of a SettingsFlag
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { _t } from "../../../languageHandler";
|
||||
@@ -35,7 +35,7 @@ interface IState {
|
||||
}
|
||||
|
||||
export default class SettingsFlag extends React.Component<IProps, IState> {
|
||||
private readonly id = `mx_SettingsFlag_${randomString(12)}`;
|
||||
private readonly id = `mx_SettingsFlag_${secureRandomString(12)}`;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { Ref } from "react";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
import classnames from "classnames";
|
||||
|
||||
export enum CheckboxStyle {
|
||||
@@ -33,7 +33,7 @@ export default class StyledCheckbox extends React.PureComponent<IProps, IState>
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
// 56^10 so unlikely chance of collision.
|
||||
this.id = this.props.id || "checkbox_" + randomString(10);
|
||||
this.id = this.props.id || "checkbox_" + secureRandomString(10);
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
ContentHelpers,
|
||||
M_BEACON,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
import classNames from "classnames";
|
||||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
@@ -81,10 +81,10 @@ const useBeaconState = (
|
||||
// eg thread and main timeline, reply
|
||||
// maplibregl needs a unique id to attach the map instance to
|
||||
const useUniqueId = (eventId: string): string => {
|
||||
const [id, setId] = useState(`${eventId}_${randomString(8)}`);
|
||||
const [id, setId] = useState(`${eventId}_${secureRandomString(8)}`);
|
||||
|
||||
useEffect(() => {
|
||||
setId(`${eventId}_${randomString(8)}`);
|
||||
setId(`${eventId}_${secureRandomString(8)}`);
|
||||
}, [eventId]);
|
||||
|
||||
return id;
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
import { MatrixEvent, ClientEvent, ClientEventHandlerMap } from "matrix-js-sdk/src/matrix";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { Tooltip } from "@vector-im/compound-web";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
@@ -41,7 +41,7 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
|
||||
|
||||
// multiple instances of same map might be in document
|
||||
// eg thread and main timeline, reply
|
||||
const idSuffix = `${props.mxEvent.getId()}_${randomString(8)}`;
|
||||
const idSuffix = `${props.mxEvent.getId()}_${secureRandomString(8)}`;
|
||||
this.mapId = `mx_MLocationBody_${idSuffix}`;
|
||||
|
||||
this.reconnectedListener = createReconnectedListener(this.clearError);
|
||||
|
||||
@@ -10,7 +10,6 @@ import React, { createRef, KeyboardEvent, RefObject } from "react";
|
||||
import classNames from "classnames";
|
||||
import { flatMap } from "lodash";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import Autocompleter, { ICompletion, ISelectionRange, IProviderCompletions } from "../../../autocomplete/Autocompleter";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
@@ -173,7 +172,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
const deferred = defer<void>();
|
||||
const deferred = Promise.withResolvers<void>();
|
||||
this.setState(
|
||||
{
|
||||
completions,
|
||||
|
||||
@@ -88,12 +88,10 @@ function getHeaderLabelJSX(vm: MemberListViewState): React.ReactNode {
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
const filteredMemberCount = vm.members.length;
|
||||
if (filteredMemberCount === 0) {
|
||||
if (vm.memberCount === 0) {
|
||||
return _t("member_list|no_matches");
|
||||
}
|
||||
return _t("member_list|count", { count: filteredMemberCount });
|
||||
return _t("member_list|count", { count: vm.memberCount });
|
||||
}
|
||||
|
||||
export const MemberListHeaderView: React.FC<Props> = (props: Props) => {
|
||||
|
||||
@@ -11,7 +11,11 @@ import { List, ListRowProps } from "react-virtualized/dist/commonjs/List";
|
||||
import { AutoSizer } from "react-virtualized";
|
||||
|
||||
import { Flex } from "../../../utils/Flex";
|
||||
import { useMemberListViewModel } from "../../../viewmodels/memberlist/MemberListViewModel";
|
||||
import {
|
||||
MemberWithSeparator,
|
||||
SEPARATOR,
|
||||
useMemberListViewModel,
|
||||
} from "../../../viewmodels/memberlist/MemberListViewModel";
|
||||
import { RoomMemberTileView } from "./tiles/RoomMemberTileView";
|
||||
import { ThreePidInviteTileView } from "./tiles/ThreePidInviteTileView";
|
||||
import { MemberListHeaderView } from "./MemberListHeaderView";
|
||||
@@ -26,10 +30,41 @@ interface IProps {
|
||||
const MemberListView: React.FC<IProps> = (props: IProps) => {
|
||||
const vm = useMemberListViewModel(props.roomId);
|
||||
|
||||
const memberCount = vm.members.length;
|
||||
const totalRows = vm.members.length;
|
||||
|
||||
const getRowComponent = (item: MemberWithSeparator): React.JSX.Element => {
|
||||
if (item === SEPARATOR) {
|
||||
return <hr className="mx_MemberListView_separator" />;
|
||||
} else if (item.member) {
|
||||
return <RoomMemberTileView member={item.member} showPresence={vm.isPresenceEnabled} />;
|
||||
} else {
|
||||
return <ThreePidInviteTileView threePidInvite={item.threePidInvite} />;
|
||||
}
|
||||
};
|
||||
|
||||
const getRowHeight = ({ index }: { index: number }): number => {
|
||||
if (vm.members[index] === SEPARATOR) {
|
||||
/**
|
||||
* This is a separator of 2px height rendered between
|
||||
* joined and invited members.
|
||||
*/
|
||||
return 2;
|
||||
} else if (totalRows && index === totalRows) {
|
||||
/**
|
||||
* The empty spacer div rendered at the bottom should
|
||||
* have a height of 32px.
|
||||
*/
|
||||
return 32;
|
||||
} else {
|
||||
/**
|
||||
* The actual member tiles have a height of 56px.
|
||||
*/
|
||||
return 56;
|
||||
}
|
||||
};
|
||||
|
||||
const rowRenderer = ({ key, index, style }: ListRowProps): React.JSX.Element => {
|
||||
if (index === memberCount) {
|
||||
if (index === totalRows) {
|
||||
// We've rendered all the members,
|
||||
// now we render an empty div to add some space to the end of the list.
|
||||
return <div key={key} style={style} />;
|
||||
@@ -37,11 +72,7 @@ const MemberListView: React.FC<IProps> = (props: IProps) => {
|
||||
const item = vm.members[index];
|
||||
return (
|
||||
<div key={key} style={style}>
|
||||
{item.member ? (
|
||||
<RoomMemberTileView member={item.member} showPresence={vm.isPresenceEnabled} />
|
||||
) : (
|
||||
<ThreePidInviteTileView threePidInvite={item.threePidInvite} />
|
||||
)}
|
||||
{getRowComponent(item)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -63,11 +94,9 @@ const MemberListView: React.FC<IProps> = (props: IProps) => {
|
||||
{({ height, width }) => (
|
||||
<List
|
||||
rowRenderer={rowRenderer}
|
||||
// All the member tiles will have a height of 56px.
|
||||
// The additional empty div at the end of the list should have a height of 32px.
|
||||
rowHeight={({ index }) => (index === memberCount ? 32 : 56)}
|
||||
rowHeight={getRowHeight}
|
||||
// The +1 refers to the additional empty div that we render at the end of the list.
|
||||
rowCount={memberCount + 1}
|
||||
rowCount={totalRows + 1}
|
||||
// Subtract the height of MemberlistHeaderView so that the parent div does not overflow.
|
||||
height={height - 113}
|
||||
width={width}
|
||||
|
||||
@@ -14,7 +14,8 @@ import { E2EIconView } from "./common/E2EIconView";
|
||||
import AvatarPresenceIconView from "./common/PresenceIconView";
|
||||
import BaseAvatar from "../../../avatars/BaseAvatar";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import { MemberTileLayout } from "./common/MemberTileLayout";
|
||||
import { MemberTileView } from "./common/MemberTileView";
|
||||
import { InvitedIconView } from "./common/InvitedIconView";
|
||||
|
||||
interface IProps {
|
||||
member: RoomMember;
|
||||
@@ -43,25 +44,23 @@ export function RoomMemberTileView(props: IProps): JSX.Element {
|
||||
presenceJSX = <AvatarPresenceIconView presenceState={presenceState} />;
|
||||
}
|
||||
|
||||
let userLabelJSX;
|
||||
if (vm.userLabel) {
|
||||
userLabelJSX = <div className="mx_MemberTileView_user_label">{vm.userLabel}</div>;
|
||||
}
|
||||
|
||||
let e2eIcon;
|
||||
let iconJsx;
|
||||
if (vm.e2eStatus) {
|
||||
e2eIcon = <E2EIconView status={vm.e2eStatus} />;
|
||||
iconJsx = <E2EIconView status={vm.e2eStatus} />;
|
||||
}
|
||||
if (member.isInvite) {
|
||||
iconJsx = <InvitedIconView isThreePid={false} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MemberTileLayout
|
||||
<MemberTileView
|
||||
title={vm.title}
|
||||
onClick={vm.onClick}
|
||||
avatarJsx={av}
|
||||
presenceJsx={presenceJSX}
|
||||
nameJsx={nameJSX}
|
||||
userLabelJsx={userLabelJSX}
|
||||
e2eIconJsx={e2eIcon}
|
||||
userLabel={vm.userLabel}
|
||||
iconJsx={iconJsx}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import React from "react";
|
||||
import { useThreePidTileViewModel } from "../../../../viewmodels/memberlist/tiles/ThreePidTileViewModel";
|
||||
import { ThreePIDInvite } from "../../../../../models/rooms/ThreePIDInvite";
|
||||
import BaseAvatar from "../../../avatars/BaseAvatar";
|
||||
import { MemberTileLayout } from "./common/MemberTileLayout";
|
||||
import { MemberTileView } from "./common/MemberTileView";
|
||||
import { InvitedIconView } from "./common/InvitedIconView";
|
||||
|
||||
interface Props {
|
||||
threePidInvite: ThreePIDInvite;
|
||||
@@ -19,5 +20,15 @@ interface Props {
|
||||
export function ThreePidInviteTileView(props: Props): JSX.Element {
|
||||
const vm = useThreePidTileViewModel(props);
|
||||
const av = <BaseAvatar name={vm.name} size="32px" aria-hidden="true" />;
|
||||
return <MemberTileLayout nameJsx={vm.name} avatarJsx={av} onClick={vm.onClick} />;
|
||||
const iconJsx = <InvitedIconView isThreePid={true} />;
|
||||
|
||||
return (
|
||||
<MemberTileView
|
||||
nameJsx={vm.name}
|
||||
avatarJsx={av}
|
||||
onClick={vm.onClick}
|
||||
userLabel={vm.userLabel}
|
||||
iconJsx={iconJsx}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import EmailIcon from "@vector-im/compound-design-tokens/assets/web/icons/email-solid";
|
||||
import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add-solid";
|
||||
|
||||
import { Flex } from "../../../../../utils/Flex";
|
||||
|
||||
interface Props {
|
||||
isThreePid: boolean;
|
||||
}
|
||||
|
||||
export function InvitedIconView({ isThreePid }: Props): JSX.Element {
|
||||
const Icon = isThreePid ? EmailIcon : UserAddIcon;
|
||||
return (
|
||||
<Flex align="center" className="mx_InvitedIconView">
|
||||
<Icon height="16px" width="16px" />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -15,11 +15,16 @@ interface Props {
|
||||
onClick: () => void;
|
||||
title?: string;
|
||||
presenceJsx?: JSX.Element;
|
||||
userLabelJsx?: JSX.Element;
|
||||
e2eIconJsx?: JSX.Element;
|
||||
userLabel?: React.ReactNode;
|
||||
iconJsx?: JSX.Element;
|
||||
}
|
||||
|
||||
export function MemberTileLayout(props: Props): JSX.Element {
|
||||
export function MemberTileView(props: Props): JSX.Element {
|
||||
let userLabelJsx: React.ReactNode;
|
||||
if (props.userLabel) {
|
||||
userLabelJsx = <div className="mx_MemberTileView_userLabel">{props.userLabel}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
// The wrapping div is required to make the magic mouse listener work, for some reason.
|
||||
<div>
|
||||
@@ -31,8 +36,8 @@ export function MemberTileLayout(props: Props): JSX.Element {
|
||||
<div className="mx_MemberTileView_name">{props.nameJsx}</div>
|
||||
</div>
|
||||
<div className="mx_MemberTileView_right">
|
||||
{props.userLabelJsx}
|
||||
{props.e2eIconJsx}
|
||||
{userLabelJsx}
|
||||
{props.iconJsx}
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { lazy, Suspense, useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import { discoverAndValidateOIDCIssuerWellKnown, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import Modal from "../../../../../Modal";
|
||||
@@ -98,7 +97,7 @@ const useSignOut = (
|
||||
const url = getManageDeviceUrl(delegatedAuthAccountUrl, deviceId);
|
||||
window.open(url, "_blank");
|
||||
} else {
|
||||
const deferredSuccess = defer<boolean>();
|
||||
const deferredSuccess = Promise.withResolvers<boolean>();
|
||||
await deleteDevicesWithInteractiveAuth(matrixClient, deviceIds, async (success) => {
|
||||
deferredSuccess.resolve(success);
|
||||
});
|
||||
|
||||
@@ -2379,7 +2379,7 @@
|
||||
"all_rooms_home_description": "Alle Räume, denen du beigetreten bist, werden auf der Startseite erscheinen.",
|
||||
"always_show_message_timestamps": "Nachrichtenzeitstempel immer anzeigen",
|
||||
"appearance": {
|
||||
"bundled_emoji_font": "Verwenden Sie den mitgelieferten Emoji Font.",
|
||||
"bundled_emoji_font": "Mitgelieferte Emoji-Schriftart verwenden",
|
||||
"compact_layout": "Kompakten Text und Nachrichten anzeigen",
|
||||
"compact_layout_description": "Um diese Funktion nutzen zu können, muss das moderne Nachrichtenlayout ausgewählt sein.",
|
||||
"custom_font": "Systemschriftart verwenden",
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership, Membership } from "matrix-js-sdk/src/types";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { NamespacedValue } from "matrix-js-sdk/src/NamespacedValue";
|
||||
import { IWidgetApiRequest } from "matrix-widget-api";
|
||||
@@ -743,7 +743,7 @@ export class ElementCall extends Call {
|
||||
const url = ElementCall.generateWidgetUrl(client, roomId);
|
||||
return WidgetStore.instance.addVirtualWidget(
|
||||
{
|
||||
id: randomString(24), // So that it's globally unique
|
||||
id: secureRandomString(24), // So that it's globally unique
|
||||
creatorUserId: client.getUserId()!,
|
||||
name: "Element Call",
|
||||
type: WidgetType.CALL.preferred,
|
||||
|
||||
@@ -31,7 +31,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
// the frequency with which we flush to indexeddb
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
|
||||
import { getCircularReplacer } from "../utils/JSON";
|
||||
|
||||
@@ -135,7 +135,7 @@ export class IndexedDBLogStore {
|
||||
private indexedDB: IDBFactory,
|
||||
private logger: ConsoleLogger,
|
||||
) {
|
||||
this.id = "instance-" + randomString(16);
|
||||
this.id = "instance-" + secureRandomString(16);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { AccountDataEvents, ClientEvent, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import { isEqual } from "lodash";
|
||||
|
||||
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
|
||||
@@ -159,7 +158,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
||||
|
||||
// Attach a deferred *before* setting the account data to ensure we catch any requests
|
||||
// which race between different lines.
|
||||
const deferred = defer<void>();
|
||||
const deferred = Promise.withResolvers<void>();
|
||||
const handler = (event: MatrixEvent): void => {
|
||||
if (event.getType() !== eventType || !isEqual(event.getContent<AccountDataEvents[K]>()[field], value))
|
||||
return;
|
||||
|
||||
@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { MatrixClient, MatrixEvent, Room, RoomEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
|
||||
import { objectClone, objectKeyChanges } from "../../utils/objects";
|
||||
@@ -96,7 +95,7 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
|
||||
|
||||
await this.client.setRoomAccountData(roomId, eventType, content);
|
||||
|
||||
const deferred = defer<void>();
|
||||
const deferred = Promise.withResolvers<void>();
|
||||
const handler = (event: MatrixEvent, room: Room): void => {
|
||||
if (room.roomId !== roomId || event.getType() !== eventType) return;
|
||||
if (field !== null && event.getContent()[field] !== value) return;
|
||||
|
||||
@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { MatrixClient, MatrixEvent, RoomState, RoomStateEvent, StateEvents } from "matrix-js-sdk/src/matrix";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
|
||||
import { objectClone, objectKeyChanges } from "../../utils/objects";
|
||||
@@ -92,7 +91,7 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
|
||||
|
||||
const { event_id: eventId } = await this.client.sendStateEvent(roomId, eventType, content);
|
||||
|
||||
const deferred = defer<void>();
|
||||
const deferred = Promise.withResolvers<void>();
|
||||
const handler = (event: MatrixEvent): void => {
|
||||
if (event.getId() !== eventId) return;
|
||||
this.client.off(RoomStateEvent.Events, handler);
|
||||
|
||||
@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { MatrixError, MatrixClient, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { AddressType, getAddressType } from "../UserAddress";
|
||||
@@ -51,7 +50,7 @@ export default class MultiInviter {
|
||||
private _fatal = false;
|
||||
private completionStates: CompletionStates = {}; // State of each address (invited or error)
|
||||
private errors: Record<string, IError> = {}; // { address: {errorText, errcode} }
|
||||
private deferred: IDeferred<CompletionStates> | null = null;
|
||||
private deferred: PromiseWithResolvers<CompletionStates> | null = null;
|
||||
private reason: string | undefined;
|
||||
|
||||
/**
|
||||
@@ -93,7 +92,7 @@ export default class MultiInviter {
|
||||
};
|
||||
}
|
||||
}
|
||||
this.deferred = defer<CompletionStates>();
|
||||
this.deferred = Promise.withResolvers<CompletionStates>();
|
||||
this.inviteMore(0);
|
||||
|
||||
return this.deferred.promise;
|
||||
|
||||
@@ -6,8 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { IDeferred, defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
/**
|
||||
A countdown timer, exposing a promise api.
|
||||
A timer starts in a non-started state,
|
||||
@@ -22,7 +20,7 @@ a new one through `clone()` or `cloneIfRun()`.
|
||||
export default class Timer {
|
||||
private timerHandle?: number;
|
||||
private startTs?: number;
|
||||
private deferred!: IDeferred<void>;
|
||||
private deferred!: PromiseWithResolvers<void>;
|
||||
|
||||
public constructor(private timeout: number) {
|
||||
this.setNotStarted();
|
||||
@@ -31,7 +29,7 @@ export default class Timer {
|
||||
private setNotStarted(): void {
|
||||
this.timerHandle = undefined;
|
||||
this.startTs = undefined;
|
||||
this.deferred = defer();
|
||||
this.deferred = Promise.withResolvers();
|
||||
this.deferred.promise = this.deferred.promise.finally(() => {
|
||||
this.timerHandle = undefined;
|
||||
});
|
||||
|
||||
@@ -9,12 +9,13 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { base32 } from "rfc4648";
|
||||
import { capitalize } from "lodash";
|
||||
import { IWidget, IWidgetData } from "matrix-widget-api";
|
||||
import { Room, ClientEvent, MatrixClient, RoomStateEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { randomString, randomLowercaseString, randomUppercaseString } from "matrix-js-sdk/src/randomstring";
|
||||
import { LOWERCASE, secureRandomString, secureRandomStringFrom } from "matrix-js-sdk/src/randomstring";
|
||||
|
||||
import PlatformPeg from "../PlatformPeg";
|
||||
import SdkConfig from "../SdkConfig";
|
||||
@@ -427,7 +428,10 @@ export default class WidgetUtils {
|
||||
): Promise<void> {
|
||||
const domain = Jitsi.getInstance().preferredDomain;
|
||||
const auth = (await Jitsi.getInstance().getJitsiAuth()) ?? undefined;
|
||||
const widgetId = randomString(24); // Must be globally unique
|
||||
|
||||
// Must be globally unique, although predicatablity is not important, the js-sdk has functions to generate
|
||||
// secure ranom strings, and speed is not important here.
|
||||
const widgetId = secureRandomString(24);
|
||||
|
||||
let confId: string;
|
||||
if (auth === "openidtoken-jwt") {
|
||||
@@ -437,8 +441,8 @@ export default class WidgetUtils {
|
||||
// https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
|
||||
confId = base32.stringify(new TextEncoder().encode(roomId), { pad: false });
|
||||
} else {
|
||||
// Create a random conference ID
|
||||
confId = `Jitsi${randomUppercaseString(1)}${randomLowercaseString(23)}`;
|
||||
// Create a random conference ID (capitalised so the name looks sensible in Jitsi)
|
||||
confId = `Jitsi${capitalize(secureRandomStringFrom(24, LOWERCASE))}`;
|
||||
}
|
||||
|
||||
// TODO: Remove URL hacks when the mobile clients eventually support v2 widgets
|
||||
|
||||
@@ -13,7 +13,6 @@ import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import escapeHtml from "escape-html";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import Exporter from "./Exporter";
|
||||
import { mediaFromMxc } from "../../customisations/Media";
|
||||
@@ -302,7 +301,7 @@ export default class HTMLExporter extends Exporter {
|
||||
if (hasAvatar) await this.saveAvatarIfNeeded(mxEv);
|
||||
// We have to wait for the component to be rendered before we can get the markup
|
||||
// so pass a deferred as a ref to the component.
|
||||
const deferred = defer<void>();
|
||||
const deferred = Promise.withResolvers<void>();
|
||||
const EventTile = this.getEventTile(mxEv, continuation, deferred.resolve);
|
||||
let eventTileMarkup: string;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { completeAuthorizationCodeGrant, generateOidcAuthorizationUrl } from "matrix-js-sdk/src/oidc/authorize";
|
||||
import { QueryDict } from "matrix-js-sdk/src/utils";
|
||||
import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { IdTokenClaims } from "oidc-client-ts";
|
||||
|
||||
import { OidcClientError } from "./error";
|
||||
@@ -34,7 +34,7 @@ export const startOidcLogin = async (
|
||||
): Promise<void> => {
|
||||
const redirectUri = PlatformPeg.get()!.getOidcCallbackUrl().href;
|
||||
|
||||
const nonce = randomString(10);
|
||||
const nonce = secureRandomString(10);
|
||||
|
||||
const prompt = isRegistration ? "create" : undefined;
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ function checkBrowserFeatures(): boolean {
|
||||
window.Modernizr.addTest("promiseprototypefinally", () => typeof window.Promise?.prototype?.finally === "function");
|
||||
// ES2020: http://262.ecma-international.org/#sec-promise.allsettled
|
||||
window.Modernizr.addTest("promiseallsettled", () => typeof window.Promise?.allSettled === "function");
|
||||
// ES2024: https://2ality.com/2024/05/proposal-promise-with-resolvers.html
|
||||
window.Modernizr.addTest("promisewithresolvers", () => typeof window.Promise?.withResolvers === "function");
|
||||
// ES2018: https://262.ecma-international.org/9.0/#sec-get-regexp.prototype.dotAll
|
||||
window.Modernizr.addTest(
|
||||
"regexpdotall",
|
||||
|
||||
@@ -12,7 +12,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { MatrixClient, Room, MatrixEvent, OidcRegistrationClientMetadata } from "matrix-js-sdk/src/matrix";
|
||||
import React from "react";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import BasePlatform, { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform";
|
||||
@@ -93,7 +93,7 @@ export default class ElectronPlatform extends BasePlatform {
|
||||
private readonly ipc = new IPCManager("ipcCall", "ipcReply");
|
||||
private readonly eventIndexManager: BaseEventIndexManager = new SeshatIndexManager();
|
||||
// this is the opaque token we pass to the HS which when we get it in our callback we can resolve to a profile
|
||||
private readonly ssoID: string = randomString(32);
|
||||
private readonly ssoID: string = secureRandomString(32);
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
@@ -5,7 +5,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { ElectronChannel } from "../../@types/global";
|
||||
@@ -17,7 +16,7 @@ interface IPCPayload {
|
||||
}
|
||||
|
||||
export class IPCManager {
|
||||
private pendingIpcCalls: { [ipcCallId: number]: IDeferred<any> } = {};
|
||||
private pendingIpcCalls: { [ipcCallId: number]: PromiseWithResolvers<any> } = {};
|
||||
private nextIpcCallId = 0;
|
||||
|
||||
public constructor(
|
||||
@@ -33,7 +32,7 @@ export class IPCManager {
|
||||
public async call(name: string, ...args: any[]): Promise<any> {
|
||||
// TODO this should be moved into the preload.js file.
|
||||
const ipcCallId = ++this.nextIpcCallId;
|
||||
const deferred = defer<any>();
|
||||
const deferred = Promise.withResolvers<any>();
|
||||
this.pendingIpcCalls[ipcCallId] = deferred;
|
||||
// Maybe add a timeout to these? Probably not necessary.
|
||||
window.electron!.send(this.sendChannel, { id: ipcCallId, name, args });
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import "@testing-library/jest-dom";
|
||||
import "blob-polyfill";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import { PredictableRandom } from "./test-utils/predictableRandom"; // https://github.com/jsdom/jsdom/issues/2555
|
||||
@@ -25,7 +25,8 @@ jest.mock("matrix-js-sdk/src/randomstring");
|
||||
beforeEach(() => {
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const mockRandom = new PredictableRandom();
|
||||
mocked(randomString).mockImplementation((len) => {
|
||||
// needless to say, the mock is not cryptographically secure
|
||||
mocked(secureRandomString).mockImplementation((len) => {
|
||||
let ret = "";
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const v = mockRandom.get() * chars.length;
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
M_POLL_RESPONSE,
|
||||
M_TEXT,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
|
||||
import { flushPromises } from "./utilities";
|
||||
|
||||
@@ -67,7 +67,7 @@ export const makePollEndEvent = (
|
||||
id?: string,
|
||||
): MatrixEvent => {
|
||||
return new MatrixEvent({
|
||||
event_id: id || randomString(16),
|
||||
event_id: id || secureRandomString(16),
|
||||
room_id: roomId,
|
||||
origin_server_ts: ts,
|
||||
type: M_POLL_END.name,
|
||||
@@ -91,7 +91,7 @@ export const makePollResponseEvent = (
|
||||
ts = 0,
|
||||
): MatrixEvent =>
|
||||
new MatrixEvent({
|
||||
event_id: randomString(16),
|
||||
event_id: secureRandomString(16),
|
||||
room_id: roomId,
|
||||
origin_server_ts: ts,
|
||||
type: M_POLL_RESPONSE.name,
|
||||
|
||||
@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { mocked } from "jest-mock";
|
||||
import { ISendEventResponse, MatrixClient, RelationType, UploadResponse } from "matrix-js-sdk/src/matrix";
|
||||
import { ImageInfo } from "matrix-js-sdk/src/types";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import encrypt, { IEncryptedFile } from "matrix-encrypt-attachment";
|
||||
|
||||
import ContentMessages, { UploadCanceledError, uploadFile } from "../../src/ContentMessages";
|
||||
@@ -333,7 +332,7 @@ describe("ContentMessages", () => {
|
||||
|
||||
describe("cancelUpload", () => {
|
||||
it("should cancel in-flight upload", async () => {
|
||||
const deferred = defer<UploadResponse>();
|
||||
const deferred = Promise.withResolvers<UploadResponse>();
|
||||
mocked(client.uploadContent).mockReturnValue(deferred.promise);
|
||||
const file1 = new File([], "file1");
|
||||
const prom = contentMessages.sendContentToRoom(file1, roomId, undefined, client, undefined);
|
||||
|
||||
@@ -21,7 +21,7 @@ import { completeAuthorizationCodeGrant } from "matrix-js-sdk/src/oidc/authorize
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { OidcError } from "matrix-js-sdk/src/oidc/error";
|
||||
import { BearerTokenResponse } from "matrix-js-sdk/src/oidc/validate";
|
||||
import { defer, IDeferred, sleep } from "matrix-js-sdk/src/utils";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import MatrixChat from "../../../../src/components/structures/MatrixChat";
|
||||
@@ -79,7 +79,7 @@ describe("<MatrixChat />", () => {
|
||||
const deviceId = "qwertyui";
|
||||
const accessToken = "abc123";
|
||||
const refreshToken = "def456";
|
||||
let bootstrapDeferred: IDeferred<void>;
|
||||
let bootstrapDeferred: PromiseWithResolvers<void>;
|
||||
// reused in createClient mock below
|
||||
const getMockClientMethods = () => ({
|
||||
...mockClientMethodsUser(userId),
|
||||
@@ -245,7 +245,7 @@ describe("<MatrixChat />", () => {
|
||||
{} as ValidatedServerConfig,
|
||||
);
|
||||
|
||||
bootstrapDeferred = defer();
|
||||
bootstrapDeferred = Promise.withResolvers();
|
||||
|
||||
await clearAllModals();
|
||||
});
|
||||
@@ -1439,7 +1439,7 @@ describe("<MatrixChat />", () => {
|
||||
jest.spyOn(MatrixJs, "createClient").mockReturnValue(client);
|
||||
|
||||
// intercept initCrypto and have it block until we complete the deferred
|
||||
const initCryptoCompleteDefer = defer();
|
||||
const initCryptoCompleteDefer = Promise.withResolvers<void>();
|
||||
const initCryptoCalled = new Promise<void>((resolve) => {
|
||||
client.initRustCrypto.mockImplementation(() => {
|
||||
resolve();
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
SearchResult,
|
||||
ISearchResults,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { RoomSearchView } from "../../../../src/components/structures/RoomSearchView";
|
||||
import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
||||
@@ -53,7 +52,7 @@ describe("<RoomSearchView/>", () => {
|
||||
});
|
||||
|
||||
it("should show a spinner before the promise resolves", async () => {
|
||||
const deferred = defer<ISearchResults>();
|
||||
const deferred = Promise.withResolvers<ISearchResults>();
|
||||
|
||||
render(
|
||||
<RoomSearchView
|
||||
@@ -267,7 +266,7 @@ describe("<RoomSearchView/>", () => {
|
||||
});
|
||||
|
||||
it("should handle resolutions after unmounting sanely", async () => {
|
||||
const deferred = defer<ISearchResults>();
|
||||
const deferred = Promise.withResolvers<ISearchResults>();
|
||||
|
||||
const { unmount } = render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
@@ -291,7 +290,7 @@ describe("<RoomSearchView/>", () => {
|
||||
});
|
||||
|
||||
it("should handle rejections after unmounting sanely", async () => {
|
||||
const deferred = defer<ISearchResults>();
|
||||
const deferred = Promise.withResolvers<ISearchResults>();
|
||||
|
||||
const { unmount } = render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
@@ -315,7 +314,7 @@ describe("<RoomSearchView/>", () => {
|
||||
});
|
||||
|
||||
it("should show modal if error is encountered", async () => {
|
||||
const deferred = defer<ISearchResults>();
|
||||
const deferred = Promise.withResolvers<ISearchResults>();
|
||||
|
||||
render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
cleanup,
|
||||
} from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import {
|
||||
stubClient,
|
||||
@@ -303,7 +302,7 @@ describe("RoomView", () => {
|
||||
it("should not display the timeline when the room encryption is loading", async () => {
|
||||
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
|
||||
jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
|
||||
const deferred = defer<boolean>();
|
||||
const deferred = Promise.withResolvers<boolean>();
|
||||
jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockImplementation(() => deferred.promise);
|
||||
|
||||
const { asFragment, container } = await mountRoomView();
|
||||
|
||||
@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen } from "jest-matrix-react";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import PowerSelector from "../../../../../src/components/views/elements/PowerSelector";
|
||||
|
||||
@@ -70,7 +69,7 @@ describe("<PowerSelector />", () => {
|
||||
});
|
||||
|
||||
it("should reset when onChange promise rejects", async () => {
|
||||
const deferred = defer<void>();
|
||||
const deferred = Promise.withResolvers<void>();
|
||||
render(
|
||||
<PowerSelector
|
||||
value={25}
|
||||
|
||||
@@ -12,7 +12,6 @@ import userEvent from "@testing-library/user-event";
|
||||
import { Mocked, mocked } from "jest-mock";
|
||||
import { Room, User, MatrixClient, RoomMember, MatrixEvent, EventType, Device } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import { EventEmitter } from "events";
|
||||
import {
|
||||
UserVerificationStatus,
|
||||
@@ -795,7 +794,7 @@ describe("<DeviceItem />", () => {
|
||||
});
|
||||
|
||||
it("when userId is the same as userId from client, uses isCrossSigningVerified to determine if button is shown", async () => {
|
||||
const deferred = defer<DeviceVerificationStatus>();
|
||||
const deferred = Promise.withResolvers<DeviceVerificationStatus>();
|
||||
mockCrypto.getDeviceVerificationStatus.mockReturnValue(deferred.promise);
|
||||
|
||||
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
|
||||
@@ -1058,7 +1057,7 @@ describe("<UserOptionsSection />", () => {
|
||||
])(
|
||||
"clicking »message« %s should start a DM",
|
||||
async (test: string, member: RoomMember | User, expectedAvatarUrl: string | undefined) => {
|
||||
const deferred = defer<string>();
|
||||
const deferred = Promise.withResolvers<string>();
|
||||
mocked(startDmOnFirstMessage).mockReturnValue(deferred.promise);
|
||||
|
||||
renderComponent({ member });
|
||||
|
||||
@@ -224,7 +224,29 @@ exports[`MemberTileView ThreePidInviteTileView renders ThreePidInvite correctly
|
||||
</div>
|
||||
<div
|
||||
class="mx_MemberTileView_right"
|
||||
/>
|
||||
>
|
||||
<div
|
||||
class="mx_MemberTileView_userLabel"
|
||||
>
|
||||
(Invited)
|
||||
</div>
|
||||
<div
|
||||
class="mx_Flex mx_InvitedIconView"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="16px"
|
||||
viewBox="0 0 24 24"
|
||||
width="16px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Zm0 5.111a1 1 0 0 0 .514.874l7 3.89a1 1 0 0 0 .972 0l7-3.89a1 1 0 1 0-.972-1.748L12 11.856 5.486 8.237A1 1 0 0 0 4 9.111Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen, within } from "jest-matrix-react";
|
||||
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import EventIndexPanel from "../../../../../src/components/views/settings/EventIndexPanel";
|
||||
import EventIndexPeg from "../../../../../src/indexing/EventIndexPeg";
|
||||
@@ -140,9 +139,9 @@ describe("<EventIndexPanel />", () => {
|
||||
});
|
||||
it("enables event indexing on enable button click", async () => {
|
||||
jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(true);
|
||||
let deferredInitEventIndex: IDeferred<boolean> | undefined;
|
||||
let deferredInitEventIndex: PromiseWithResolvers<boolean> | undefined;
|
||||
jest.spyOn(EventIndexPeg, "initEventIndex").mockImplementation(() => {
|
||||
deferredInitEventIndex = defer<boolean>();
|
||||
deferredInitEventIndex = Promise.withResolvers<boolean>();
|
||||
return deferredInitEventIndex.promise;
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
Visibility,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import {
|
||||
clearAllModals,
|
||||
@@ -160,7 +159,7 @@ describe("<JoinRuleSettings />", () => {
|
||||
});
|
||||
|
||||
it(`upgrades room when changing join rule to ${joinRule}`, async () => {
|
||||
const deferredInvites: IDeferred<any>[] = [];
|
||||
const deferredInvites: PromiseWithResolvers<any>[] = [];
|
||||
// room that doesn't support the join rule
|
||||
const room = new Room(roomId, client, userId);
|
||||
const parentSpace = new Room("!parentSpace:server.org", client, userId);
|
||||
@@ -183,7 +182,7 @@ describe("<JoinRuleSettings />", () => {
|
||||
// resolve invites by hand
|
||||
// flushPromises is too blunt to test reliably
|
||||
client.invite.mockImplementation(() => {
|
||||
const p = defer<{}>();
|
||||
const p = Promise.withResolvers<{}>();
|
||||
deferredInvites.push(p);
|
||||
return p.promise;
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
IThreepid,
|
||||
ThreepidMedium,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
@@ -287,7 +287,7 @@ describe("<Notifications />", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
let i = 0;
|
||||
mocked(randomString).mockImplementation(() => {
|
||||
mocked(secureRandomString).mockImplementation(() => {
|
||||
return "testid_" + i++;
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import { fireEvent, getByRole, render, RenderResult, screen, waitFor } from "jes
|
||||
import { MatrixClient, EventType, MatrixEvent, Room, RoomMember, ISendEventResponse } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { mocked } from "jest-mock";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import RolesRoomSettingsTab from "../../../../../../../src/components/views/settings/tabs/room/RolesRoomSettingsTab";
|
||||
@@ -202,7 +201,7 @@ describe("RolesRoomSettingsTab", () => {
|
||||
});
|
||||
|
||||
it("should roll back power level change on error", async () => {
|
||||
const deferred = defer<ISendEventResponse>();
|
||||
const deferred = Promise.withResolvers<ISendEventResponse>();
|
||||
mocked(cli.sendStateEvent).mockReturnValue(deferred.promise);
|
||||
mocked(cli.getRoom).mockReturnValue(room);
|
||||
// @ts-ignore - mocked doesn't support overloads properly
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "jest-matrix-react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { CryptoApi, DeviceVerificationStatus, VerificationRequest } from "matrix-js-sdk/src/crypto-api";
|
||||
import { defer, sleep } from "matrix-js-sdk/src/utils";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import {
|
||||
ClientEvent,
|
||||
Device,
|
||||
@@ -894,7 +894,7 @@ describe("<SessionManagerTab />", () => {
|
||||
});
|
||||
|
||||
it("deletes a device when interactive auth is not required", async () => {
|
||||
const deferredDeleteMultipleDevices = defer<{}>();
|
||||
const deferredDeleteMultipleDevices = Promise.withResolvers<{}>();
|
||||
mockClient.deleteMultipleDevices.mockReturnValue(deferredDeleteMultipleDevices.promise);
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
|
||||
@@ -1103,7 +1103,7 @@ describe("<SessionManagerTab />", () => {
|
||||
// get a handle for resolving the delete call
|
||||
// because promise flushing after the confirm modal is resolving this too
|
||||
// and we want to test the loading state here
|
||||
const resolveDeleteRequest = defer<IAuthData>();
|
||||
const resolveDeleteRequest = Promise.withResolvers<IAuthData>();
|
||||
mockClient.deleteMultipleDevices.mockImplementation(() => {
|
||||
return resolveDeleteRequest.promise;
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { act, fireEvent, render, RenderResult } from "jest-matrix-react";
|
||||
import { EventType, MatrixClient, Room, GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
@@ -92,7 +92,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
let i = 0;
|
||||
mocked(randomString).mockImplementation(() => {
|
||||
mocked(secureRandomString).mockImplementation(() => {
|
||||
return "testid_" + i++;
|
||||
});
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../src/dispatcher/actions";
|
||||
import { AsyncActionPayload } from "../../../src/dispatcher/payloads";
|
||||
@@ -20,8 +18,8 @@ describe("MatrixDispatcher", () => {
|
||||
});
|
||||
|
||||
it("should execute callbacks in registered order", async () => {
|
||||
const deferred1 = defer<number>();
|
||||
const deferred2 = defer<number>();
|
||||
const deferred1 = Promise.withResolvers<number>();
|
||||
const deferred2 = Promise.withResolvers<number>();
|
||||
|
||||
const fn1 = jest.fn(() => deferred1.resolve(1));
|
||||
const fn2 = jest.fn(() => deferred2.resolve(2));
|
||||
@@ -36,8 +34,8 @@ describe("MatrixDispatcher", () => {
|
||||
});
|
||||
|
||||
it("should skip the queue for the given callback", async () => {
|
||||
const deferred1 = defer<number>();
|
||||
const deferred2 = defer<number>();
|
||||
const deferred1 = Promise.withResolvers<number>();
|
||||
const deferred2 = Promise.withResolvers<number>();
|
||||
|
||||
const fn1 = jest.fn(() => deferred1.resolve(1));
|
||||
const fn2 = jest.fn(() => deferred2.resolve(2));
|
||||
|
||||
@@ -6,7 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import ServerSupportUnstableFeatureController from "../../../../src/settings/controllers/ServerSupportUnstableFeatureController";
|
||||
@@ -34,7 +33,7 @@ describe("ServerSupportUnstableFeatureController", () => {
|
||||
controller,
|
||||
};
|
||||
|
||||
const deferred = defer<any>();
|
||||
const deferred = Promise.withResolvers<any>();
|
||||
watchers.watchSetting(setting, null, deferred.resolve);
|
||||
MatrixClientBackedController.matrixClient = cli;
|
||||
await deferred.promise;
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
RoomState,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import SpaceStore from "../../../src/stores/spaces/SpaceStore";
|
||||
import {
|
||||
@@ -1008,7 +1007,7 @@ describe("SpaceStore", () => {
|
||||
|
||||
await run();
|
||||
|
||||
const deferred = defer<boolean>();
|
||||
const deferred = Promise.withResolvers<boolean>();
|
||||
space.loadMembersIfNeeded.mockImplementation(() => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
|
||||
@@ -49,7 +49,7 @@ describe("OIDC authorization", () => {
|
||||
origin: baseUrl,
|
||||
};
|
||||
|
||||
jest.spyOn(randomStringUtils, "randomString").mockRestore();
|
||||
jest.spyOn(randomStringUtils, "secureRandomString").mockRestore();
|
||||
mockPlatformPeg();
|
||||
Object.defineProperty(window, "crypto", {
|
||||
value: {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"outDir": "./lib",
|
||||
"declaration": true,
|
||||
"jsx": "react",
|
||||
"lib": ["es2022", "dom", "dom.iterable"],
|
||||
"lib": ["es2022", "es2024.promise", "dom", "dom.iterable"],
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"jest-matrix-react": ["./test/test-utils/jest-matrix-react"]
|
||||
|
||||
@@ -3529,9 +3529,8 @@
|
||||
ts-xor "^1.3.0"
|
||||
vaul "^1.0.0"
|
||||
|
||||
"@vector-im/matrix-wysiwyg-wasm@link:../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.0-af862ffd231dc0a6b8d6f2cb3601e68456c0ff24-integrity/node_modules/bindings/wysiwyg-wasm":
|
||||
"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.0-af862ffd231dc0a6b8d6f2cb3601e68456c0ff24-integrity/node_modules/bindings/wysiwyg-wasm":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@vector-im/matrix-wysiwyg@2.38.0":
|
||||
version "2.38.0"
|
||||
@@ -8240,9 +8239,9 @@ jwt-decode@4.0.0, jwt-decode@^4.0.0:
|
||||
integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==
|
||||
|
||||
katex@^0.16.0:
|
||||
version "0.16.11"
|
||||
resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.11.tgz#4bc84d5584f996abece5f01c6ad11304276a33f5"
|
||||
integrity sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==
|
||||
version "0.16.21"
|
||||
resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.21.tgz#8f63c659e931b210139691f2cc7bb35166b792a3"
|
||||
integrity sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==
|
||||
dependencies:
|
||||
commander "^8.3.0"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user