Merge branch 'develop' of github.com:vector-im/element-web into langley/use_list_view_with_room_list
6
.github/workflows/docker.yaml
vendored
@ -37,14 +37,14 @@ jobs:
|
||||
install: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@ -96,7 +96,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
images: |
|
||||
|
||||
@ -70,5 +70,13 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
],
|
||||
"property-no-deprecated": [
|
||||
true,
|
||||
{
|
||||
ignoreProperties: ["-webkit-box-orient", "word-wrap"],
|
||||
},
|
||||
],
|
||||
"nesting-selector-no-missing-scoping-root": null,
|
||||
"no-invalid-position-declaration": null,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# syntax=docker.io/docker/dockerfile:1.17-labs@sha256:9187104f31e3a002a8a6a3209ea1f937fb7486c093cbbde1e14b0fa0d7e4f1b5
|
||||
|
||||
# Builder
|
||||
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:a80324457a2c8d09c83ff9edf2bdf71f378d3288de920e68a358bd3c484b8c4a AS builder
|
||||
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:2d63e0f812d023c4c764e83d7e30dc94949304443ebc372d5c445e63a5ae49c1 AS builder
|
||||
|
||||
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
||||
ARG USE_CUSTOM_SDKS=false
|
||||
@ -19,7 +19,7 @@ RUN /src/scripts/docker-package.sh
|
||||
RUN cp /src/config.sample.json /src/webapp/config.json
|
||||
|
||||
# App
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:86df552d36eb24c45d3f5becf6423bd056a3fd235d7085fe3d5ea28ba89a8232
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:e61b77b27c8f3124fad6d19e894ca5b603bcaf6a34a2df035511299dfa6fad35
|
||||
|
||||
# Need root user to install packages & manipulate the usr directory
|
||||
USER root
|
||||
|
||||
22
package.json
@ -73,10 +73,10 @@
|
||||
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/pretty-format/react-is": "19.1.0",
|
||||
"@playwright/test": "1.54.1",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"**/pretty-format/react-is": "19.1.1",
|
||||
"@playwright/test": "1.54.2",
|
||||
"@types/react": "19.1.9",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"oidc-client-ts": "3.3.0",
|
||||
"jwt-decode": "4.0.0",
|
||||
"caniuse-lite": "1.0.30001724",
|
||||
@ -94,10 +94,10 @@
|
||||
"@matrix-org/emojibase-bindings": "^1.3.4",
|
||||
"@matrix-org/react-sdk-module-api": "^2.4.0",
|
||||
"@matrix-org/spec": "^1.7.0",
|
||||
"@sentry/browser": "^9.0.0",
|
||||
"@sentry/browser": "^10.0.0",
|
||||
"@types/png-chunks-extract": "^1.0.2",
|
||||
"@types/react-virtualized": "^9.21.30",
|
||||
"@vector-im/compound-design-tokens": "^5.0.0",
|
||||
"@vector-im/compound-design-tokens": "^6.0.0",
|
||||
"@vector-im/compound-web": "^8.1.2",
|
||||
"@vector-im/matrix-wysiwyg": "2.39.0",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
@ -186,7 +186,7 @@
|
||||
"@babel/preset-typescript": "^7.12.7",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@casualbot/jest-sonar-reporter": "2.2.7",
|
||||
"@element-hq/element-call-embedded": "0.13.1",
|
||||
"@element-hq/element-call-embedded": "0.14.1",
|
||||
"@element-hq/element-web-playwright-common": "^1.4.4",
|
||||
"@peculiar/webcrypto": "^1.4.3",
|
||||
"@playwright/test": "^1.50.1",
|
||||
@ -223,9 +223,9 @@
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/pako": "^2.0.0",
|
||||
"@types/qrcode": "^1.3.5",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react": "19.1.9",
|
||||
"@types/react-beautiful-dnd": "^13.0.0",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/semver": "^7.5.8",
|
||||
@ -300,8 +300,8 @@
|
||||
"semver": "^7.5.2",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"storybook": "^9.0.12",
|
||||
"stylelint": "^16.13.0",
|
||||
"stylelint-config-standard": "^38.0.0",
|
||||
"stylelint": "^16.23.0",
|
||||
"stylelint-config-standard": "^39.0.0",
|
||||
"stylelint-scss": "^6.0.0",
|
||||
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
|
||||
@ -40,7 +40,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
// wait for the tile to finish loading
|
||||
await expect(
|
||||
page
|
||||
.locator(".mx_AudioPlayer_mediaName")
|
||||
.getByTestId("audio-player-name")
|
||||
.last()
|
||||
.filter({ hasText: file.split("/").at(-1) }),
|
||||
).toBeVisible();
|
||||
@ -55,12 +55,10 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
// Check that the audio player is rendered and its button becomes visible
|
||||
const checkPlayerVisibility = async (locator: Locator) => {
|
||||
// Assert that the audio player and media information are visible
|
||||
const mediaInfo = locator.locator(
|
||||
".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container .mx_AudioPlayer_mediaInfo",
|
||||
);
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName", { hasText: ".ogg" })).toBeVisible(); // extension
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible();
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size
|
||||
const mediaInfo = locator.getByRole("region", { name: "Audio player" });
|
||||
await expect(mediaInfo.getByText(".ogg")).toBeVisible(); // extension
|
||||
await expect(mediaInfo.getByRole("time")).toHaveText("00:01"); // duration
|
||||
await expect(mediaInfo.getByText("(3.56 KB)")).toBeVisible(); // actual size;
|
||||
|
||||
// Assert that the play button can be found and is visible
|
||||
await expect(locator.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
@ -79,7 +77,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
}
|
||||
|
||||
// Check the status of the seek bar
|
||||
expect(await page.locator(".mx_AudioPlayer_seek input[type='range']").count()).toBeGreaterThan(0);
|
||||
expect(await page.getByRole("region", { name: "Audio player" }).getByRole("slider").count()).toBeGreaterThan(0);
|
||||
|
||||
// Enable IRC layout
|
||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||
@ -101,7 +99,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
display: none !important;
|
||||
}
|
||||
`,
|
||||
mask: [page.locator(".mx_AudioPlayer_seek")],
|
||||
mask: [page.getByTestId("audio-player-seek")],
|
||||
};
|
||||
|
||||
// Take a snapshot of mx_EventTile_last on IRC layout
|
||||
@ -187,9 +185,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
const container = page.locator(".mx_EventTile_last .mx_AudioPlayer_container");
|
||||
const container = page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" });
|
||||
// Assert that the counter is zero before clicking the play button
|
||||
await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(container.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Find and click "Play" button, the wait is to make the test less flaky
|
||||
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
@ -199,7 +197,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await expect(container.getByRole("button", { name: "Pause" })).toBeVisible();
|
||||
|
||||
// Assert that the timer is reset when the audio file finished playing
|
||||
await expect(container.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(container.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Assert that "Play" button can be found
|
||||
await expect(container.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
@ -227,7 +225,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||
|
||||
// Assert the audio player is rendered
|
||||
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
||||
await expect(page.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||
|
||||
// Find and click "Reply" button on MessageActionBar
|
||||
const tile = page.locator(".mx_EventTile_last");
|
||||
@ -237,7 +235,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible();
|
||||
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||
|
||||
// Assert that replied audio file is rendered as file button inside ReplyChain
|
||||
const button = tile.locator(".mx_ReplyChain_wrapper .mx_MFileBody_info[role='button']");
|
||||
@ -262,7 +260,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
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 expect(
|
||||
page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
|
||||
).toBeVisible();
|
||||
|
||||
await clickButtonReply(tile);
|
||||
|
||||
@ -270,7 +270,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await uploadFile(page, "playwright/sample-files/upload-second.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(page.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
||||
await expect(
|
||||
page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
|
||||
).toBeVisible();
|
||||
|
||||
await clickButtonReply(tile);
|
||||
|
||||
@ -278,7 +280,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await uploadFile(page, "playwright/sample-files/upload-third.ogg");
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(tile.locator(".mx_AudioPlayer_container")).toBeVisible();
|
||||
await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||
|
||||
// Assert that there are two "mx_ReplyChain" elements
|
||||
await expect(tile.locator(".mx_ReplyChain")).toHaveCount(2);
|
||||
@ -314,7 +316,9 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
// On the main timeline
|
||||
const messageList = page.locator(".mx_RoomView_MessageList");
|
||||
// Assert the audio player is rendered
|
||||
await expect(messageList.locator(".mx_EventTile_last .mx_AudioPlayer_container")).toBeVisible();
|
||||
await expect(
|
||||
messageList.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }),
|
||||
).toBeVisible();
|
||||
// Find and click "Reply in thread" button
|
||||
await messageList.locator(".mx_EventTile_last").hover();
|
||||
await messageList.locator(".mx_EventTile_last").getByRole("button", { name: "Reply in thread" }).click();
|
||||
@ -322,10 +326,10 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
// On a thread
|
||||
const thread = page.locator(".mx_ThreadView");
|
||||
const threadTile = thread.locator(".mx_EventTile_last");
|
||||
const audioPlayer = threadTile.locator(".mx_AudioPlayer_container");
|
||||
const audioPlayer = threadTile.getByRole("region", { name: "Audio player" });
|
||||
|
||||
// Assert that the counter is zero before clicking the play button
|
||||
await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(audioPlayer.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Find and click "Play" button, the wait is to make the test less flaky
|
||||
await expect(audioPlayer.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
@ -335,7 +339,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await expect(audioPlayer.getByRole("button", { name: "Pause" })).toBeVisible();
|
||||
|
||||
// Assert that the timer is reset when the audio file finished playing
|
||||
await expect(audioPlayer.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(audioPlayer.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Assert that "Play" button can be found
|
||||
await expect(audioPlayer.getByRole("button", { name: "Play" })).not.toBeDisabled();
|
||||
|
||||
@ -28,7 +28,7 @@ test.describe("Composer", () => {
|
||||
|
||||
test.describe("CIDER", () => {
|
||||
test("sends a message when you click send or press Enter", async ({ page }) => {
|
||||
const composer = page.getByRole("textbox", { name: "Send a message…" });
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
|
||||
// Type a message
|
||||
await composer.pressSequentially("my message 0");
|
||||
@ -52,7 +52,7 @@ test.describe("Composer", () => {
|
||||
});
|
||||
|
||||
test("can write formatted text", async ({ page }) => {
|
||||
const composer = page.getByRole("textbox", { name: "Send a message…" });
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
|
||||
await composer.pressSequentially("my bold");
|
||||
await composer.press(`${CtrlOrMeta}+KeyB`);
|
||||
@ -68,7 +68,7 @@ test.describe("Composer", () => {
|
||||
await page.getByTestId("mx_EmojiPicker").locator(".mx_EmojiPicker_item", { hasText: "😇" }).click();
|
||||
|
||||
await page.locator(".mx_ContextualMenu_background").click(); // Close emoji picker
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).press("Enter"); // Send message
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter"); // Send message
|
||||
|
||||
await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible();
|
||||
});
|
||||
@ -79,7 +79,7 @@ test.describe("Composer", () => {
|
||||
});
|
||||
|
||||
test("only sends when you press Control+Enter", async ({ page }) => {
|
||||
const composer = page.getByRole("textbox", { name: "Send a message…" });
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
// Type a message and press Enter
|
||||
await composer.pressSequentially("my message 3");
|
||||
await composer.press("Enter");
|
||||
|
||||
@ -91,10 +91,10 @@ test.describe("Key backup reset from elsewhere", () => {
|
||||
|
||||
await csAPI.deleteBackupVersion(backupInfo.version);
|
||||
|
||||
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("/discardsession");
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).fill("/discardsession");
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("Message with broken key backup");
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).fill("Message with broken key backup");
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
// Should be the message we sent plus the room creation event
|
||||
|
||||
@ -154,8 +154,8 @@ test.describe("Cryptography", function () {
|
||||
await app.client.bootstrapCrossSigning(aliceCredentials);
|
||||
await startDMWithBob(page, bob);
|
||||
// send first message
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).fill("Hey!");
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).press("Enter");
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hey!");
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter");
|
||||
await checkDMRoom(page);
|
||||
const bobRoomId = await bobJoin(page, bob);
|
||||
await expect(page.locator(".mx_MessageComposer_e2eIcon")).toMatchScreenshot("composer-e2e-icon-normal.png");
|
||||
|
||||
@ -209,7 +209,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||
const dialog = page.locator(".mx_Dialog");
|
||||
// We use `pressSequentially` here to make sure that the FocusLock isn't causing us any problems
|
||||
// (cf https://github.com/element-hq/element-web/issues/30089)
|
||||
await dialog.locator("textarea").pressSequentially(recoveryKey);
|
||||
await dialog.getByTitle("Recovery key").pressSequentially(recoveryKey);
|
||||
await dialog.getByRole("button", { name: "Continue", disabled: false }).click();
|
||||
|
||||
await page.getByRole("button", { name: "Done" }).click();
|
||||
|
||||
@ -228,7 +228,7 @@ export async function logIntoElement(page: Page, credentials: Credentials, secur
|
||||
await useSecurityKey.click();
|
||||
}
|
||||
// Fill in the recovery key
|
||||
await page.locator(".mx_Dialog").locator("textarea").fill(securityKey);
|
||||
await page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey);
|
||||
await page.getByRole("button", { name: "Continue", disabled: false }).click();
|
||||
await page.getByRole("button", { name: "Done" }).click();
|
||||
}
|
||||
@ -263,7 +263,7 @@ export async function verifySession(app: ElementAppPage, securityKey: string) {
|
||||
const settings = await app.settings.openUserSettings("Encryption");
|
||||
await settings.getByRole("button", { name: "Verify this device" }).click();
|
||||
await app.page.getByRole("button", { name: "Verify with Recovery Key" }).click();
|
||||
await app.page.locator(".mx_Dialog").locator("textarea").fill(securityKey);
|
||||
await app.page.locator(".mx_Dialog").getByTitle("Recovery key").fill(securityKey);
|
||||
await app.page.getByRole("button", { name: "Continue", disabled: false }).click();
|
||||
await app.page.getByRole("button", { name: "Done" }).click();
|
||||
await app.settings.closeDialog();
|
||||
|
||||
@ -13,7 +13,7 @@ import { type Locator, type Page } from "@playwright/test";
|
||||
import { test, expect } from "../../element-web-test";
|
||||
|
||||
async function sendMessage(page: Page, message: string): Promise<Locator> {
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).fill(message);
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).fill(message);
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
const msgTile = page.locator(".mx_EventTile_last");
|
||||
@ -22,7 +22,7 @@ async function sendMessage(page: Page, message: string): Promise<Locator> {
|
||||
}
|
||||
|
||||
async function sendMultilineMessages(page: Page, messages: string[]) {
|
||||
await page.getByRole("textbox", { name: "Send a message…" }).focus();
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted message…" }).focus();
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
await page.keyboard.type(messages[i]);
|
||||
if (i < messages.length - 1) await page.keyboard.press("Shift+Enter");
|
||||
@ -40,7 +40,7 @@ async function replyMessage(page: Page, message: Locator, replyMessage: string):
|
||||
await line.hover();
|
||||
await line.getByRole("button", { name: "Reply", exact: true }).click();
|
||||
|
||||
await page.getByRole("textbox", { name: "Send a reply…" }).fill(replyMessage);
|
||||
await page.getByRole("textbox", { name: "Send an unencrypted reply…" }).fill(replyMessage);
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
const msgTile = page.locator(".mx_EventTile_last");
|
||||
|
||||
@ -29,7 +29,7 @@ test.describe("Pills", () => {
|
||||
|
||||
// send a message using the built-in room mention functionality (autocomplete)
|
||||
await page
|
||||
.getByRole("textbox", { name: "Send a message…" })
|
||||
.getByRole("textbox", { name: "Send an unencrypted message…" })
|
||||
.pressSequentially(`Hello world! Join here: #${targetLocalpart.substring(0, 3)}`);
|
||||
await page.locator(".mx_Autocomplete_Completion_title").click();
|
||||
await page.getByRole("button", { name: "Send message" }).click();
|
||||
|
||||
@ -63,9 +63,7 @@ test.describe("FilePanel", () => {
|
||||
await expect(roomViewBody.locator(".mx_EventTile[data-layout='group'] img[alt='riot.png']")).toBeVisible();
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
await expect(
|
||||
roomViewBody.locator(".mx_EventTile[data-layout='group'] .mx_AudioPlayer_container"),
|
||||
).toBeVisible();
|
||||
await expect(roomViewBody.getByRole("region", { name: "Audio player" })).toBeVisible();
|
||||
|
||||
// Assert that the file button exists
|
||||
await expect(
|
||||
@ -97,9 +95,7 @@ test.describe("FilePanel", () => {
|
||||
await expect(image.locator("img[alt='riot.png']")).toBeVisible();
|
||||
|
||||
// Detect the audio file
|
||||
const audio = filePanelMessageList.locator(
|
||||
".mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
|
||||
);
|
||||
const audio = filePanelMessageList.getByRole("region", { name: "Audio player" });
|
||||
// Assert that the play button is rendered
|
||||
await expect(audio.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
|
||||
@ -130,7 +126,7 @@ test.describe("FilePanel", () => {
|
||||
// Take a snapshot of file tiles list on FilePanel
|
||||
await expect(filePanelMessageList).toMatchScreenshot("file-tiles-list.png", {
|
||||
// Exclude timestamps & flaky seek bar from snapshot
|
||||
mask: [page.locator(".mx_MessageTimestamp, .mx_AudioPlayer_seek")],
|
||||
mask: [page.locator(".mx_MessageTimestamp"), page.getByTestId("audio-player-seek")],
|
||||
});
|
||||
});
|
||||
|
||||
@ -138,21 +134,19 @@ test.describe("FilePanel", () => {
|
||||
// Upload an image file
|
||||
await uploadFile(page, "playwright/sample-files/1sec.ogg");
|
||||
|
||||
const audioBody = page.locator(
|
||||
".mx_FilePanel .mx_RoomView_MessageList .mx_EventTile_mediaLine .mx_MAudioBody .mx_AudioPlayer_container",
|
||||
);
|
||||
const audioBody = page.getByTestId("right-panel").getByRole("region", { name: "Audio player" });
|
||||
|
||||
// Assert that the audio player is rendered
|
||||
// Assert that the audio file information is rendered
|
||||
const mediaInfo = audioBody.locator(".mx_AudioPlayer_mediaInfo");
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_mediaName").getByText("1sec.ogg")).toBeVisible();
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "00:01" })).toBeVisible();
|
||||
await expect(mediaInfo.locator(".mx_AudioPlayer_byline", { hasText: "(3.56 KB)" })).toBeVisible(); // actual size
|
||||
// Assert that the audio file information is rendered;
|
||||
await expect(audioBody.getByText("1sec.ogg")).toBeVisible(); // extension
|
||||
await expect(audioBody.getByRole("time")).toHaveText("00:01"); // duration
|
||||
await expect(audioBody.getByText("(3.56 KB)")).toBeVisible(); // actual size;
|
||||
|
||||
// Assert that the duration counter is 00:01 before clicking the play button
|
||||
await expect(audioBody.locator(".mx_AudioPlayer_mediaInfo time", { hasText: "00:01" })).toBeVisible();
|
||||
await expect(audioBody.getByRole("time")).toHaveText("00:01");
|
||||
|
||||
// Assert that the counter is zero before clicking the play button
|
||||
await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(audioBody.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Click the play button
|
||||
await audioBody.getByRole("button", { name: "Play" }).click();
|
||||
@ -161,7 +155,7 @@ test.describe("FilePanel", () => {
|
||||
await expect(audioBody.getByRole("button", { name: "Pause" })).toBeVisible();
|
||||
|
||||
// Assert that the timer is reset when the audio file finished playing
|
||||
await expect(audioBody.locator(".mx_AudioPlayer_seek [role='timer']", { hasText: "00:00" })).toBeVisible();
|
||||
await expect(audioBody.getByRole("timer")).toHaveText("00:00");
|
||||
|
||||
// Assert that the play button is rendered
|
||||
await expect(audioBody.getByRole("button", { name: "Play" })).toBeVisible();
|
||||
|
||||
@ -30,7 +30,7 @@ async function startDM(app: ElementAppPage, page: Page, name: string): Promise<v
|
||||
await result.first().click();
|
||||
|
||||
// send first message to start DM
|
||||
const locator = page.getByRole("textbox", { name: "Send a message…" });
|
||||
const locator = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await expect(locator).toBeFocused();
|
||||
await locator.fill("Hey!");
|
||||
await locator.press("Enter");
|
||||
@ -260,7 +260,7 @@ test.describe("Spotlight", () => {
|
||||
|
||||
// Send first message to actually start DM
|
||||
await expect(roomHeaderName(page)).toHaveText(bot2.credentials.displayName);
|
||||
const locator = page.getByRole("textbox", { name: "Send a message…" });
|
||||
const locator = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await locator.fill("Hey!");
|
||||
await locator.press("Enter");
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ test.describe("Threads", () => {
|
||||
|
||||
const roomViewLocator = page.locator(".mx_RoomView_body");
|
||||
// User sends message
|
||||
const textbox = roomViewLocator.getByRole("textbox", { name: "Send a message…" });
|
||||
const textbox = roomViewLocator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill("Hello Mr. Bot");
|
||||
await textbox.press("Enter");
|
||||
|
||||
@ -108,7 +108,7 @@ test.describe("Threads", () => {
|
||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
|
||||
|
||||
// User responds in thread
|
||||
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send a message…" });
|
||||
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await locator.fill("Test");
|
||||
await locator.press("Enter");
|
||||
|
||||
@ -262,7 +262,7 @@ test.describe("Threads", () => {
|
||||
await locator.locator(".mx_EventTile_line").click();
|
||||
|
||||
// User responds & asserts
|
||||
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send a message…" });
|
||||
locator = page.locator(".mx_ThreadView").getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await locator.fill("Great!");
|
||||
await locator.press("Enter");
|
||||
|
||||
@ -335,8 +335,8 @@ test.describe("Threads", () => {
|
||||
|
||||
// Send message
|
||||
const locator = page.locator(".mx_RoomView_body");
|
||||
await locator.getByRole("textbox", { name: "Send a message…" }).fill("Hello Mr. Bot");
|
||||
await locator.getByRole("textbox", { name: "Send a message…" }).press("Enter");
|
||||
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hello Mr. Bot");
|
||||
await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter");
|
||||
// Create thread
|
||||
const locator2 = locator.locator(".mx_EventTile[data-scroll-tokens]").filter({ hasText: "Hello Mr. Bot" });
|
||||
await locator2.hover();
|
||||
@ -366,7 +366,7 @@ test.describe("Threads", () => {
|
||||
|
||||
let locator = page.locator(".mx_RoomView_body");
|
||||
// User sends message
|
||||
let textbox = locator.getByRole("textbox", { name: "Send a message…" });
|
||||
let textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill("Hello Mr. Bot");
|
||||
await textbox.press("Enter");
|
||||
// Wait for message to send, get its ID and save as @threadId
|
||||
@ -395,7 +395,7 @@ test.describe("Threads", () => {
|
||||
locator = page.locator(".mx_ThreadView");
|
||||
await locator.locator(".mx_EventTile_last").hover();
|
||||
await locator.locator(".mx_EventTile_last").getByRole("button", { name: "Reply" }).click();
|
||||
textbox = locator.getByRole("textbox", { name: "Reply to thread…" });
|
||||
textbox = locator.getByRole("textbox", { name: "Reply to unencrypted thread…" });
|
||||
await textbox.fill("Please come here");
|
||||
await textbox.press("Enter");
|
||||
// Wait until the reply is sent
|
||||
@ -414,7 +414,7 @@ test.describe("Threads", () => {
|
||||
|
||||
// Send message
|
||||
let locator = page.locator(".mx_RoomView_body");
|
||||
let textbox = locator.getByRole("textbox", { name: "Send a message…" });
|
||||
let textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill("Hello Mr. Bot");
|
||||
await textbox.press("Enter");
|
||||
// Create thread
|
||||
@ -425,7 +425,7 @@ test.describe("Threads", () => {
|
||||
|
||||
// Send message to thread
|
||||
locator = page.locator(".mx_ThreadPanel");
|
||||
textbox = locator.getByRole("textbox", { name: "Send a message…" });
|
||||
textbox = locator.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill("Hello Mr. User");
|
||||
await textbox.press("Enter");
|
||||
await expect(locator.locator(".mx_EventTile_last").getByText("Hello Mr. User")).toBeAttached();
|
||||
@ -456,7 +456,7 @@ test.describe("Threads", () => {
|
||||
*/
|
||||
const sendMessage = async (message: string) => {
|
||||
const messageComposer = page.getByRole("region", { name: "Message composer" });
|
||||
const textbox = messageComposer.getByRole("textbox", { name: "Send a message…" });
|
||||
const textbox = messageComposer.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill(message);
|
||||
await textbox.press("Enter");
|
||||
};
|
||||
@ -478,7 +478,7 @@ test.describe("Threads", () => {
|
||||
|
||||
// Send a message in the thread
|
||||
const threadPanel = page.locator(".mx_ThreadPanel");
|
||||
const textbox = threadPanel.getByRole("textbox", { name: "Send a message…" });
|
||||
const textbox = threadPanel.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await textbox.fill(threadMessage);
|
||||
await textbox.press("Enter");
|
||||
await expect(threadPanel.locator(".mx_EventTile_last").getByText(threadMessage)).toBeVisible();
|
||||
|
||||
@ -461,11 +461,11 @@ test.describe("Timeline", () => {
|
||||
// Send a emote
|
||||
await page
|
||||
.locator(".mx_RoomView_body")
|
||||
.getByRole("textbox", { name: "Send a message…" })
|
||||
.getByRole("textbox", { name: "Send an unencrypted message…" })
|
||||
.fill("/me says hello to Mr. Bot");
|
||||
await page
|
||||
.locator(".mx_RoomView_body")
|
||||
.getByRole("textbox", { name: "Send a message…" })
|
||||
.getByRole("textbox", { name: "Send an unencrypted message…" })
|
||||
.press("Enter");
|
||||
// Check inline start margin of its avatar
|
||||
// Here --right-padding is for the avatar on the message line
|
||||
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.3 KiB |
@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
|
||||
|
||||
const TAG = "develop@sha256:2f6fff14ff23f356705abdbf2ed62c3dd6ca2103cef4ae813714ddc199bbd76a";
|
||||
const TAG = "develop@sha256:a0d8db97e39321166959cecdc4c684daff36fefdc7968de47be7e0fdb1cb4541";
|
||||
|
||||
/**
|
||||
* SynapseContainer which freezes the docker digest to stabilise tests,
|
||||
|
||||
@ -95,7 +95,6 @@
|
||||
@import "./structures/auth/_Registration.pcss";
|
||||
@import "./structures/auth/_SessionLockStolenView.pcss";
|
||||
@import "./structures/auth/_SetupEncryptionBody.pcss";
|
||||
@import "./views/audio_messages/_AudioPlayer.pcss";
|
||||
@import "./views/audio_messages/_PlayPauseButton.pcss";
|
||||
@import "./views/audio_messages/_PlaybackContainer.pcss";
|
||||
@import "./views/audio_messages/_SeekBar.pcss";
|
||||
|
||||
@ -15,7 +15,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
display: grid;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
grid-gap: $spacing-8;
|
||||
gap: $spacing-8;
|
||||
grid-template-columns: auto auto auto;
|
||||
grid-template-rows: auto;
|
||||
cursor: pointer;
|
||||
|
||||
@ -22,7 +22,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
display: grid;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
grid-gap: $spacing-8;
|
||||
gap: $spacing-8;
|
||||
grid-template-columns: min-content 1fr min-content;
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
@ -47,7 +47,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
.mx_PollListItemEnded_answers {
|
||||
display: grid;
|
||||
grid-gap: $spacing-8;
|
||||
gap: $spacing-8;
|
||||
margin-top: $spacing-12;
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
.mx_DeviceDetailHeading_renameForm {
|
||||
display: grid;
|
||||
grid-gap: $spacing-16;
|
||||
gap: $spacing-16;
|
||||
justify-content: left;
|
||||
grid-template-columns: 100%;
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
border-bottom: 1px solid $quinary-content;
|
||||
|
||||
display: grid;
|
||||
grid-gap: $spacing-24;
|
||||
gap: $spacing-24;
|
||||
justify-content: left;
|
||||
grid-template-columns: 100%;
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
.mx_DeviceTile_actions {
|
||||
display: grid;
|
||||
grid-gap: $spacing-8;
|
||||
gap: $spacing-8;
|
||||
grid-auto-flow: column;
|
||||
margin-left: $spacing-8;
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_FilteredDeviceList_list {
|
||||
list-style-type: none;
|
||||
display: grid;
|
||||
grid-gap: $spacing-16;
|
||||
gap: $spacing-16;
|
||||
margin: 0;
|
||||
padding: 0 $spacing-16;
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_SettingsSubsection_content {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-gap: $spacing-8;
|
||||
gap: $spacing-8;
|
||||
/* setting minwidth 0 makes columns definitely sized fixing horizontal overflow */
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
justify-items: flex-start;
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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_MediaBody.mx_AudioPlayer_container {
|
||||
padding: 16px 12px 12px 12px;
|
||||
|
||||
.mx_AudioPlayer_primaryContainer {
|
||||
display: flex;
|
||||
|
||||
.mx_PlayPauseButton {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.mx_AudioPlayer_mediaInfo {
|
||||
flex: 1;
|
||||
overflow: hidden; /* makes the ellipsis on the file name work */
|
||||
|
||||
& > * {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_AudioPlayer_mediaName {
|
||||
color: $primary-content;
|
||||
font-size: $font-15px;
|
||||
line-height: $font-15px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
padding-bottom: 4px; /* mimics the line-height differences in the Figma */
|
||||
}
|
||||
|
||||
.mx_AudioPlayer_byline {
|
||||
font-size: $font-12px;
|
||||
line-height: $font-12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AudioPlayer_seek {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_SeekBar {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mx_Clock {
|
||||
min-width: $font-42px; /* for flexbox */
|
||||
padding-left: $spacing-4; /* isolate from seek bar */
|
||||
text-align: justify;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,23 @@ Please see LICENSE files in the repository root for full details.
|
||||
}
|
||||
|
||||
.mx_AccessSecretStorageDialog_primaryContainer {
|
||||
.mx_AccessSecretStorageDialog_recoveryKeyEntry {
|
||||
/*
|
||||
* Be specific here to avoid "margin: 9px" from _common.pcss
|
||||
*/
|
||||
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) {
|
||||
input {
|
||||
/*
|
||||
* From figma: https://www.figma.com/design/ZodBLtGnKmRTGJo5SGLnH3/ER-137--Excluding-Insecure-Devices?node-id=102-43729&t=QmewENUd7f6Tmw9U-1
|
||||
*/
|
||||
width: 448px;
|
||||
height: 70px;
|
||||
margin: 0px;
|
||||
border: 1px solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AccessSecretStorageDialog_recoveryKeyFeedback {
|
||||
&::before {
|
||||
content: "";
|
||||
|
||||
@ -60,7 +60,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
.mx_MPollBody_allOptions {
|
||||
display: grid;
|
||||
grid-gap: $spacing-16;
|
||||
gap: $spacing-16;
|
||||
margin-bottom: $spacing-8;
|
||||
max-width: 550px;
|
||||
}
|
||||
|
||||
@ -21,12 +21,12 @@ Please see LICENSE files in the repository root for full details.
|
||||
flex: 1 1 0;
|
||||
align-content: flex-start;
|
||||
display: grid;
|
||||
grid-gap: $spacing-20;
|
||||
gap: $spacing-20;
|
||||
padding-right: $spacing-64;
|
||||
margin: $spacing-32 0;
|
||||
|
||||
&.mx_PollHistoryList_list_ENDED {
|
||||
grid-gap: $spacing-32;
|
||||
gap: $spacing-32;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
.mx_SettingsSubsection_content {
|
||||
margin-top: 12px;
|
||||
grid-gap: 12px;
|
||||
gap: 12px;
|
||||
justify-items: stretch;
|
||||
justify-content: stretch;
|
||||
}
|
||||
@ -40,7 +40,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
}
|
||||
|
||||
.mx_NotificationSettings2_flags {
|
||||
grid-gap: 4px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.mx_StyledRadioButton_content {
|
||||
|
||||
@ -11,7 +11,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
display: grid;
|
||||
grid-template-columns: auto repeat(3, 62px);
|
||||
place-items: center center;
|
||||
grid-gap: 8px;
|
||||
gap: 8px;
|
||||
|
||||
/* Override StyledRadioButton default styles */
|
||||
.mx_StyledRadioButton {
|
||||
|
||||
@ -34,7 +34,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_SettingsSection_subSections {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-gap: $spacing-32;
|
||||
gap: $spacing-32;
|
||||
|
||||
padding: $spacing-16 0;
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_SettingsTab_sections {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-gap: $spacing-32;
|
||||
gap: $spacing-32;
|
||||
|
||||
padding-bottom: $spacing-16;
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-gap: $spacing-4;
|
||||
gap: $spacing-4;
|
||||
}
|
||||
|
||||
.mx_KeyboardShortcut_shortcutRow,
|
||||
|
||||
@ -6,7 +6,7 @@ sonar.organization=element-hq
|
||||
|
||||
sonar.sources=src,res
|
||||
sonar.tests=test,playwright,src
|
||||
sonar.test.inclusions=test/*,playwright/*,src/**/*.test.tsx
|
||||
sonar.test.inclusions=test/*,playwright/*,src/**/*.test.*
|
||||
sonar.exclusions=__mocks__,docs,element.io,nginx
|
||||
|
||||
sonar.cpd.exclusions=src/i18n/strings/*.json
|
||||
|
||||
@ -13,6 +13,8 @@ import { type Optional } from "matrix-events-sdk";
|
||||
import { _t, getUserLanguage } from "./languageHandler";
|
||||
import { getUserTimezone } from "./TimezoneHandler";
|
||||
|
||||
export { formatSeconds } from "./shared-components/utils/DateUtils";
|
||||
|
||||
export const MINUTE_MS = 60000;
|
||||
export const HOUR_MS = MINUTE_MS * 60;
|
||||
export const DAY_MS = HOUR_MS * 24;
|
||||
@ -180,31 +182,6 @@ export function formatTime(date: Date, showTwelveHour = false, locale?: string):
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
export function formatSeconds(inSeconds: number): string {
|
||||
const isNegative = inSeconds < 0;
|
||||
inSeconds = Math.abs(inSeconds);
|
||||
|
||||
const hours = Math.floor(inSeconds / (60 * 60))
|
||||
.toFixed(0)
|
||||
.padStart(2, "0");
|
||||
const minutes = Math.floor((inSeconds % (60 * 60)) / 60)
|
||||
.toFixed(0)
|
||||
.padStart(2, "0");
|
||||
const seconds = Math.floor((inSeconds % (60 * 60)) % 60)
|
||||
.toFixed(0)
|
||||
.padStart(2, "0");
|
||||
|
||||
let output = "";
|
||||
if (hours !== "00") output += `${hours}:`;
|
||||
output += `${minutes}:${seconds}`;
|
||||
|
||||
if (isNegative) {
|
||||
output = "-" + output;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export function formatTimeLeft(inSeconds: number): string {
|
||||
const hours = Math.floor(inSeconds / (60 * 60)).toFixed(0);
|
||||
const minutes = Math.floor((inSeconds % (60 * 60)) / 60).toFixed(0);
|
||||
|
||||
@ -26,9 +26,9 @@ export interface IInviteResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Invites multiple addresses to a room
|
||||
* Simpler interface to utils/MultiInviter but with
|
||||
* no option to cancel.
|
||||
* Invites multiple addresses to a room.
|
||||
*
|
||||
* Simpler interface to {@link MultiInviter}.
|
||||
*
|
||||
* @param {string} roomId The ID of the room to invite to
|
||||
* @param {string[]} addresses Array of strings of addresses to invite. May be matrix IDs or 3pids.
|
||||
|
||||
@ -36,9 +36,9 @@ import { RoomSettingsTab } from "./components/views/dialogs/RoomSettingsDialog";
|
||||
import AccessibleButton from "./components/views/elements/AccessibleButton";
|
||||
import RightPanelStore from "./stores/right-panel/RightPanelStore";
|
||||
import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
|
||||
import { ElementCall } from "./models/Call";
|
||||
import { getSenderName } from "./utils/event/getSenderName";
|
||||
import PosthogTrackers from "./PosthogTrackers.ts";
|
||||
import { ElementCallEventType } from "./call-types.ts";
|
||||
|
||||
function getRoomMemberDisplayname(client: MatrixClient, event: MatrixEvent, userId = event.getSender()): string {
|
||||
const roomId = event.getRoomId();
|
||||
@ -922,7 +922,7 @@ for (const evType of ALL_RULE_TYPES) {
|
||||
}
|
||||
|
||||
// Add both stable and unstable m.call events
|
||||
for (const evType of ElementCall.CALL_EVENT_TYPE.names) {
|
||||
for (const evType of ElementCallEventType.names) {
|
||||
stateHandlers[evType] = textForCallEvent;
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ import { arrayFastResample } from "../utils/arrays";
|
||||
import { type IDestroyable } from "../utils/IDestroyable";
|
||||
import { PlaybackClock } from "./PlaybackClock";
|
||||
import { createAudioContext, decodeOgg } from "./compat";
|
||||
import { clamp } from "../utils/numbers";
|
||||
import { clamp } from "../shared-components/utils/numbers";
|
||||
import { DEFAULT_WAVEFORM, PLAYBACK_WAVEFORM_SAMPLES } from "./consts";
|
||||
import { PlaybackEncoder } from "../PlaybackEncoder";
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type IAmplitudePayload, type ITimingPayload, PayloadEvent, WORKLET_NAME } from "./consts";
|
||||
import { percentageOf } from "../utils/numbers";
|
||||
import { percentageOf } from "../shared-components/utils/numbers";
|
||||
|
||||
// from AudioWorkletGlobalScope: https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletGlobalScope
|
||||
declare const currentTime: number;
|
||||
|
||||
@ -19,7 +19,7 @@ import { PayloadEvent, WORKLET_NAME } from "./consts";
|
||||
import { UPDATE_EVENT } from "../stores/AsyncStore";
|
||||
import { createAudioContext } from "./compat";
|
||||
import { FixedRollingArray } from "../utils/FixedRollingArray";
|
||||
import { clamp } from "../utils/numbers";
|
||||
import { clamp } from "../shared-components/utils/numbers";
|
||||
import recorderWorkletFactory from "./recorderWorkletFactory";
|
||||
|
||||
const CHANNELS = 1; // stereo isn't important
|
||||
|
||||