Compare commits
33 Commits
develop
...
t3chguy/cs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20be92ab7e | ||
|
|
1afa202ef5 | ||
|
|
9d3cba1621 | ||
|
|
e6d572c413 | ||
|
|
ced4456c16 | ||
|
|
654a9eeb3a | ||
|
|
dc0763f58a | ||
|
|
19d8798dc0 | ||
|
|
d9f6355693 | ||
|
|
4613c44f47 | ||
|
|
0961e6f6e4 | ||
|
|
5c2d89a0d7 | ||
|
|
40120cc226 | ||
|
|
8fef5c5ec6 | ||
|
|
9864294575 | ||
|
|
d1a0875d7f | ||
|
|
523fba271f | ||
|
|
99690f6314 | ||
|
|
58b75e0fe3 | ||
|
|
ed59a53734 | ||
|
|
b243c94093 | ||
|
|
7cd9ec4fbd | ||
|
|
9fa23d3c19 | ||
|
|
05c64bd6bc | ||
|
|
c4d8c3e85f | ||
|
|
82390d6a23 | ||
|
|
2c2b607d3c | ||
|
|
60e574f37d | ||
|
|
d2ba05a3d8 | ||
|
|
5c28ed6738 | ||
|
|
1808a16ed3 | ||
|
|
bff234749f | ||
|
|
0733bebc38 |
@@ -351,7 +351,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
const composer = thread.locator(".mx_MessageComposer--compact");
|
||||
// Assert that the reply preview contains audio ReplyTile the file info button
|
||||
await expect(
|
||||
composer.locator(".mx_ReplyPreview .mx_ReplyTile_audio .mx_MFileBody_info[role='button']"),
|
||||
composer.locator(".mx_ReplyPreview .mx_ReplyTile .mx_MFileBody_info[role='button']"),
|
||||
).toBeVisible();
|
||||
|
||||
// Select :smile: emoji and send it
|
||||
@@ -360,6 +360,6 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
await composer.getByTestId("basicmessagecomposer").press("Enter");
|
||||
|
||||
// Assert that the file name is rendered on the file button
|
||||
await expect(threadTile.locator(".mx_ReplyTile_audio .mx_MFileBody_info[role='button']")).toBeVisible();
|
||||
await expect(threadTile.locator(".mx_ReplyTile .mx_MFileBody_info[role='button']")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,15 +31,11 @@ const startDMWithBob = async (page: Page, bob: Bot) => {
|
||||
|
||||
const testMessages = async (page: Page, bob: Bot, bobRoomId: string) => {
|
||||
// check the invite message
|
||||
await expect(
|
||||
page.locator(".mx_EventTile", { hasText: "Hey!" }).locator(".mx_EventTile_e2eIcon_warning"),
|
||||
).not.toBeVisible();
|
||||
await expect(page.locator(".mx_EventTile", { hasText: "Hey!" }).locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
|
||||
|
||||
// Bob sends a response
|
||||
await bob.sendMessage(bobRoomId, "Hoo!");
|
||||
await expect(
|
||||
page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon_warning"),
|
||||
).not.toBeVisible();
|
||||
await expect(page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
|
||||
};
|
||||
|
||||
const bobJoin = async (page: Page, bob: Bot) => {
|
||||
|
||||
@@ -30,13 +30,10 @@ test.describe("Cryptography", function () {
|
||||
test.describe("decryption failure messages", () => {
|
||||
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
|
||||
|
||||
test("should handle device-relative historical messages", async ({
|
||||
homeserver,
|
||||
page,
|
||||
app,
|
||||
credentials,
|
||||
user,
|
||||
}) => {
|
||||
test(
|
||||
"should handle device-relative historical messages",
|
||||
{ tag: "@screenshot" },
|
||||
async ({ homeserver, page, app, credentials, user }) => {
|
||||
test.setTimeout(60000);
|
||||
|
||||
// Start with a logged-in session, without key backup, and send a message.
|
||||
@@ -51,7 +48,12 @@ test.describe("Cryptography", function () {
|
||||
await app.viewRoomByName("Test room");
|
||||
const lastTile = page.locator(".mx_EventTile").last();
|
||||
await expect(lastTile).toContainText("Historical messages are not available on this device");
|
||||
await expect(lastTile.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible();
|
||||
await expect(lastTile.locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
|
||||
"This message could not be decrypted",
|
||||
);
|
||||
await expect(lastTile).toMatchScreenshot("history-not-available.png", {
|
||||
mask: [page.locator(".mx_MessageTimestamp")],
|
||||
});
|
||||
|
||||
// Now, we set up key backup, and then send another message.
|
||||
const secretStorageKey = await enableKeyBackup(app);
|
||||
@@ -77,8 +79,12 @@ test.describe("Cryptography", function () {
|
||||
expect(tiles.length).toBeGreaterThanOrEqual(2);
|
||||
// look at the last two tiles only
|
||||
for (const tile of tiles.slice(-2)) {
|
||||
await expect(tile).toContainText("You need to verify this device for access to historical messages");
|
||||
await expect(tile.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible();
|
||||
await expect(tile).toContainText(
|
||||
"You need to verify this device for access to historical messages",
|
||||
);
|
||||
await expect(tile.locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
|
||||
"This message could not be decrypted",
|
||||
);
|
||||
}
|
||||
|
||||
// Now verify our device (setting up key backup), and check what happens
|
||||
@@ -87,12 +93,17 @@ test.describe("Cryptography", function () {
|
||||
|
||||
// The first message still cannot be decrypted, because it was never backed up. It's now a regular UTD though.
|
||||
await expect(tilesAfterVerify[0]).toContainText("Unable to decrypt message");
|
||||
await expect(tilesAfterVerify[0].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible();
|
||||
await expect(tilesAfterVerify[0].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
|
||||
"This message could not be decrypted",
|
||||
);
|
||||
|
||||
// The second message should now be decrypted, with a grey shield
|
||||
await expect(tilesAfterVerify[1]).toContainText("test2 test2");
|
||||
await expect(tilesAfterVerify[1].locator(".mx_EventTile_e2eIcon_normal")).toBeVisible();
|
||||
});
|
||||
await expect(tilesAfterVerify[1].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
|
||||
"The authenticity of this encrypted message can't be guaranteed on this device.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.describe("non-joined historical messages", () => {
|
||||
test.skip(isDendrite, "does not yet support membership on events");
|
||||
@@ -186,7 +197,9 @@ test.describe("Cryptography", function () {
|
||||
// The first message from Bob was sent before Alice was in the room, so should
|
||||
// be different from the standard UTD message
|
||||
await expect(tiles[tiles.length - 5]).toContainText("You don't have access to this message");
|
||||
await expect(tiles[tiles.length - 5].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible();
|
||||
await expect(tiles[tiles.length - 5].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
|
||||
"This message could not be decrypted",
|
||||
);
|
||||
|
||||
// The second message from Bob should be decryptable
|
||||
await expect(tiles[tiles.length - 2]).toContainText("This should be decryptable");
|
||||
@@ -196,7 +209,9 @@ test.describe("Cryptography", function () {
|
||||
// in the room and is expected to be decryptable, so this should have the
|
||||
// standard UTD message
|
||||
await expect(tiles[tiles.length - 1]).toContainText("Unable to decrypt message");
|
||||
await expect(tiles[tiles.length - 1].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible();
|
||||
await expect(tiles[tiles.length - 1].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
|
||||
"This message could not be decrypted",
|
||||
);
|
||||
});
|
||||
|
||||
test("should be able to jump to a message sent before our last join event", async ({
|
||||
|
||||
@@ -68,7 +68,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||
await doTwoWaySasVerification(page, verifier);
|
||||
|
||||
await infoDialog.getByRole("button", { name: "They match" }).click();
|
||||
await expect(page.locator(".mx_E2EIcon_verified")).toMatchScreenshot("device-verified-e2eIcon.png");
|
||||
await expect(page.locator(".mx_E2EIcon")).toMatchScreenshot("device-verified-e2eIcon.png");
|
||||
await infoDialog.getByRole("button", { name: "Got it" }).click();
|
||||
|
||||
// Check that our device is now cross-signed
|
||||
|
||||
@@ -77,11 +77,8 @@ test.describe("Cryptography", function () {
|
||||
const last = page.locator(".mx_EventTile_last");
|
||||
await expect(last).toContainText("Unable to decrypt message");
|
||||
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/);
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
||||
"This message could not be decrypted",
|
||||
);
|
||||
await expect(lastE2eIcon).toHaveAccessibleName("This message could not be decrypted");
|
||||
await expect(lastE2eIcon).toMatchScreenshot("event-shield-utd.png");
|
||||
|
||||
/* Should show a red padlock for an unencrypted message in an e2e room */
|
||||
await bob.evaluate(
|
||||
@@ -99,10 +96,8 @@ test.describe("Cryptography", function () {
|
||||
);
|
||||
|
||||
await expect(last).toContainText("test unencrypted");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await expect(lastE2eIcon).toHaveAccessibleName("Not encrypted");
|
||||
await expect(lastE2eIcon).toMatchScreenshot("event-shield-warning.png");
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText("Not encrypted");
|
||||
|
||||
/* Should show no padlock for an unverified user */
|
||||
// bob sends a valid event
|
||||
@@ -133,11 +128,8 @@ test.describe("Cryptography", function () {
|
||||
/* should show red padlock for a message from an unverified device */
|
||||
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified");
|
||||
await expect(lastTile).toContainText("test encrypted from unverified");
|
||||
await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await lastTileE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText(
|
||||
"Encrypted by a device not verified by its owner.",
|
||||
);
|
||||
await expect(lastTileE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner.");
|
||||
await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png");
|
||||
|
||||
/* Should show a red padlock for a message from an unverified device.
|
||||
* Rust crypto remembers the verification state of the sending device, so it will know that the device was
|
||||
@@ -153,21 +145,15 @@ test.describe("Cryptography", function () {
|
||||
await app.viewRoomByName("TestRoom");
|
||||
|
||||
await expect(last).toContainText("test encrypted from unverified");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
||||
"Encrypted by a device not verified by its owner.",
|
||||
);
|
||||
await expect(lastE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner.");
|
||||
await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png");
|
||||
},
|
||||
);
|
||||
|
||||
test("Should show a grey padlock for a key restored from backup", async ({
|
||||
page,
|
||||
app,
|
||||
bot: bob,
|
||||
homeserver,
|
||||
user: aliceCredentials,
|
||||
}) => {
|
||||
test(
|
||||
"Should show a grey padlock for a key restored from backup",
|
||||
{ tag: "@screenshot" },
|
||||
async ({ page, app, bot: bob, homeserver, user: aliceCredentials }) => {
|
||||
test.slow();
|
||||
const securityKey = await enableKeyBackup(app);
|
||||
|
||||
@@ -200,17 +186,17 @@ test.describe("Cryptography", function () {
|
||||
/* go back to the test room and find Bob's message again */
|
||||
await app.viewRoomById(testRoomId);
|
||||
await expect(lastTile).toContainText("test encrypted 1");
|
||||
// The gray shield would be a mx_EventTile_e2eIcon_normal. The red shield would be a mx_EventTile_e2eIcon_warning.
|
||||
// The gray shield would be a Compound info icon. The red shield would be a Compound error solid icon.
|
||||
// No shield would have no div mx_EventTile_e2eIcon at all.
|
||||
await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_normal/);
|
||||
await lastTileE2eIcon.hover();
|
||||
// The key is coming from backup, so it is not anymore possible to establish if the claimed device
|
||||
// creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device."
|
||||
// It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted.
|
||||
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText(
|
||||
await expect(lastTileE2eIcon).toHaveAccessibleName(
|
||||
"The authenticity of this encrypted message can't be guaranteed on this device.",
|
||||
);
|
||||
});
|
||||
await expect(lastTileE2eIcon).toMatchScreenshot("event-shield-authenticity.png");
|
||||
},
|
||||
);
|
||||
|
||||
test("should show the correct shield on edited e2e events", async ({ page, app, bot: bob, homeserver }) => {
|
||||
// bob has a second, not cross-signed, device
|
||||
@@ -224,7 +210,7 @@ test.describe("Cryptography", function () {
|
||||
|
||||
// the message should appear, decrypted, with no warning
|
||||
await expect(
|
||||
page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon_warning"),
|
||||
page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon"),
|
||||
).not.toBeVisible();
|
||||
|
||||
// bob sends an edit to the first message with his unverified device
|
||||
@@ -241,7 +227,7 @@ test.describe("Cryptography", function () {
|
||||
|
||||
// the edit should have a warning
|
||||
await expect(
|
||||
page.locator(".mx_EventTile", { hasText: "Haa!" }).locator(".mx_EventTile_e2eIcon_warning"),
|
||||
page.locator(".mx_EventTile", { hasText: "Haa!" }).locator(".mx_EventTile_e2eIcon"),
|
||||
).toBeVisible();
|
||||
|
||||
// a second edit from the verified device should be ok
|
||||
@@ -257,16 +243,14 @@ test.describe("Cryptography", function () {
|
||||
});
|
||||
|
||||
await expect(
|
||||
page.locator(".mx_EventTile", { hasText: "Hee!" }).locator(".mx_EventTile_e2eIcon_warning"),
|
||||
page.locator(".mx_EventTile", { hasText: "Hee!" }).locator(".mx_EventTile_e2eIcon"),
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("should show correct shields on events sent by devices which have since been deleted", async ({
|
||||
page,
|
||||
app,
|
||||
bot: bob,
|
||||
homeserver,
|
||||
}) => {
|
||||
test(
|
||||
"should show correct shields on events sent by devices which have since been deleted",
|
||||
{ tag: "@screenshot" },
|
||||
async ({ page, app, bot: bob, homeserver }) => {
|
||||
// Workaround for https://github.com/element-hq/element-web/issues/28640:
|
||||
// make sure that Alice has seen Bob's identity before she goes offline. We do this by opening
|
||||
// his user info.
|
||||
@@ -294,22 +278,18 @@ test.describe("Cryptography", function () {
|
||||
const last = page.locator(".mx_EventTile_last");
|
||||
await expect(last).toContainText("test encrypted from unverified", { timeout: 20000 });
|
||||
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
||||
"Encrypted by a device not verified by its owner.",
|
||||
);
|
||||
await expect(lastE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner.");
|
||||
await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png");
|
||||
|
||||
const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" });
|
||||
await assertNoE2EIcon(penultimate, app);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("should show correct shields on events sent by users with changed identity", async ({
|
||||
page,
|
||||
app,
|
||||
bot: bob,
|
||||
homeserver,
|
||||
}) => {
|
||||
test(
|
||||
"should show correct shields on events sent by users with changed identity",
|
||||
{ tag: "@screenshot" },
|
||||
async ({ page, app, bot: bob, homeserver }) => {
|
||||
// Verify Bob
|
||||
await verify(app, bob);
|
||||
|
||||
@@ -322,14 +302,12 @@ test.describe("Cryptography", function () {
|
||||
const last = page.locator(".mx_EventTile_last");
|
||||
await expect(last).toContainText("test encrypted from user that was previously verified");
|
||||
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
|
||||
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
|
||||
await lastE2eIcon.focus();
|
||||
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
|
||||
"Sender's verified identity was reset",
|
||||
await expect(lastE2eIcon).toHaveAccessibleName("Sender's verified identity was reset");
|
||||
await expect(lastE2eIcon).toMatchScreenshot("event-shield-identity-reset.png");
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Check that the given message doesn't have an E2E warning icon.
|
||||
@@ -343,8 +321,6 @@ async function assertNoE2EIcon(messageLocator: Locator, app: ElementAppPage) {
|
||||
const e2eIcon = messageLocator.locator(".mx_EventTile_e2eIcon");
|
||||
if ((await e2eIcon.count()) > 0) {
|
||||
// uh-oh, there is an e2e icon. Let's find out what it's about so that we can throw a helpful error.
|
||||
await e2eIcon.focus();
|
||||
const tooltip = await app.getTooltipForElement(e2eIcon);
|
||||
throw new Error(`Found an unexpected e2eIcon with tooltip '${await tooltip.textContent()}'`);
|
||||
await expect(e2eIcon).toHaveAccessibleName("None");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,24 +244,6 @@ export class ElementAppPage {
|
||||
await this.page.getByRole("dialog").getByRole("button", { name: "Invite" }).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a locator for the tooltip associated with an element
|
||||
* @param e The element with the tooltip
|
||||
* @returns Locator to the tooltip
|
||||
*/
|
||||
public async getTooltipForElement(e: Locator): Promise<Locator> {
|
||||
const [labelledById, describedById] = await Promise.all([
|
||||
e.getAttribute("aria-labelledby"),
|
||||
e.getAttribute("aria-describedby"),
|
||||
]);
|
||||
if (!labelledById && !describedById) {
|
||||
throw new Error(
|
||||
"Element has no aria-labelledby or aria-describedy attributes! The tooltip should have added either one of these.",
|
||||
);
|
||||
}
|
||||
return this.page.locator(`id=${labelledById ?? describedById}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the notification toast
|
||||
*/
|
||||
|
||||
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 441 B |
|
After Width: | Height: | Size: 393 B |
|
After Width: | Height: | Size: 393 B |
|
After Width: | Height: | Size: 372 B |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
@@ -40,8 +40,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
/* that our preview is unencrypted, which doesn't actually matter */
|
||||
/* We also hide download links to not encourage users to try interacting */
|
||||
.mx_EventTile_msgOption,
|
||||
.mx_EventTile_e2eIcon_unencrypted,
|
||||
.mx_EventTile_e2eIcon_warning,
|
||||
.mx_EventTile_e2eIcon,
|
||||
.mx_MFileBody_download {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -33,14 +33,11 @@ Please see LICENSE files in the repository root for full details.
|
||||
}
|
||||
|
||||
.mx_AccessSecretStorageDialog_recoveryKeyFeedback {
|
||||
&::before {
|
||||
content: "";
|
||||
svg {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: 20px;
|
||||
margin-inline-end: 5px;
|
||||
}
|
||||
@@ -48,9 +45,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
&.mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid {
|
||||
color: $alert;
|
||||
|
||||
&::before {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
|
||||
background-color: $alert;
|
||||
svg {
|
||||
color: $alert;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,22 +63,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
}
|
||||
|
||||
&.mx_AccessibleButton_kind_confirm_sm {
|
||||
background-color: var(--cpd-color-bg-action-primary-rest);
|
||||
|
||||
&::before {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/check.svg");
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_AccessibleButton_kind_cancel_sm {
|
||||
background-color: var(--cpd-color-bg-critical-primary);
|
||||
|
||||
&::before {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/close.svg");
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_AccessibleButton_kind_icon,
|
||||
&.mx_AccessibleButton_kind_icon_primary,
|
||||
&.mx_AccessibleButton_kind_icon_primary_outline {
|
||||
@@ -114,10 +98,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&.mx_AccessibleButton_kind_secondary_content {
|
||||
color: $secondary-content;
|
||||
}
|
||||
|
||||
&.mx_AccessibleButton_kind_danger {
|
||||
color: var(--cpd-color-text-on-solid-primary);
|
||||
background-color: var(--cpd-color-bg-critical-primary);
|
||||
@@ -175,25 +155,4 @@ Please see LICENSE files in the repository root for full details.
|
||||
&.mx_AccessibleButton_kind_content_inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
&.mx_AccessibleButton_kind_confirm_sm,
|
||||
&.mx_AccessibleButton_kind_cancel_sm {
|
||||
padding: 0px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 100%;
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-color: #ffffff;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: 80%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_EventTileBubble.mx_CreateEvent {
|
||||
margin: var(--EventTileBubble_margin-block) auto;
|
||||
|
||||
&::before {
|
||||
background-color: $header-panel-text-primary-color;
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/chat-solid.svg");
|
||||
svg {
|
||||
color: $header-panel-text-primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
display: grid;
|
||||
grid-template-columns: 24px minmax(0, 1fr) min-content min-content;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
svg {
|
||||
position: relative;
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 3;
|
||||
|
||||
@@ -11,21 +11,12 @@ Please see LICENSE files in the repository root for full details.
|
||||
color: $muted-fg-color;
|
||||
vertical-align: middle;
|
||||
|
||||
padding-left: 20px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
svg {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
background-color: $muted-fg-color;
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/visibility-off.svg");
|
||||
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 0;
|
||||
display: inline-block;
|
||||
margin-right: var(--cpd-space-1-5x);
|
||||
color: $muted-fg-color;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,25 +34,17 @@ Please see LICENSE files in the repository root for full details.
|
||||
background-color: $system;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: var(--cpd-space-2x);
|
||||
vertical-align: middle;
|
||||
margin-right: 12px;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: cover;
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/attachment.svg");
|
||||
background-color: $secondary-content;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
svg {
|
||||
color: $secondary-content;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_EventTileBubble.mx_MJitsiWidgetEvent {
|
||||
&::before {
|
||||
background-color: $header-panel-text-primary-color; /* XXX: Variable abuse */
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/video-call-solid.svg");
|
||||
svg {
|
||||
color: $header-panel-text-primary-color; /* XXX: Variable abuse */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,25 +9,19 @@ Please see LICENSE files in the repository root for full details.
|
||||
color: var(--cpd-color-text-primary);
|
||||
|
||||
.mx_ReactionsRow_addReactionButton {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
visibility: hidden; /* show on hover of the .mx_EventTile */
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: var(--cpd-space-1x);
|
||||
vertical-align: middle;
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
mask-size: 16px;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
background-color: $tertiary-content;
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/reaction-add.svg");
|
||||
svg {
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
color: $tertiary-content;
|
||||
}
|
||||
|
||||
&.mx_ReactionsRow_addReactionButton_active {
|
||||
@@ -36,8 +30,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
&:hover,
|
||||
&.mx_ReactionsRow_addReactionButton_active {
|
||||
&::before {
|
||||
background-color: $primary-content;
|
||||
svg {
|
||||
color: $primary-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,20 +11,11 @@ Please see LICENSE files in the repository root for full details.
|
||||
color: $secondary-content;
|
||||
vertical-align: middle;
|
||||
|
||||
padding-left: 20px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
svg {
|
||||
margin-right: 6px;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
background-color: $icon-button-color;
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/delete.svg");
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 0;
|
||||
color: $icon-button-color;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,28 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_EventTileBubble.mx_cryptoEvent {
|
||||
margin: var(--EventTileBubble_margin-block) auto;
|
||||
|
||||
/* white infill for the transparency */
|
||||
&.mx_cryptoEvent_icon::before {
|
||||
background-color: #ffffff;
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg");
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: 80%;
|
||||
}
|
||||
|
||||
&.mx_cryptoEvent_icon::after {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg");
|
||||
background-color: $header-panel-text-primary-color;
|
||||
}
|
||||
|
||||
&.mx_cryptoEvent_icon_verified::after {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/shield.svg");
|
||||
background-color: $accent;
|
||||
}
|
||||
|
||||
&.mx_cryptoEvent_icon_warning::after {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
|
||||
background-color: $e2e-warning-color;
|
||||
&.mx_cryptoEvent_icon svg {
|
||||
color: $header-panel-text-primary-color;
|
||||
}
|
||||
|
||||
.mx_cryptoEvent_state,
|
||||
|
||||
@@ -61,24 +61,19 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
.mx_BaseCard_header_title_button--option {
|
||||
position: relative;
|
||||
width: var(--BaseCard_header-button-size);
|
||||
height: var(--BaseCard_header-button-size);
|
||||
width: calc(var(--BaseCard_header-button-size) - 4px);
|
||||
height: calc(var(--BaseCard_header-button-size) - 4px);
|
||||
padding: 2px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
inset-inline-start: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg");
|
||||
background-color: $secondary-content;
|
||||
svg {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
display: block;
|
||||
color: $secondary-content;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
background-color: $primary-content;
|
||||
&:hover svg {
|
||||
color: $primary-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,22 +21,3 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_E2EIcon.mx_E2EIcon_inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_E2EIcon_warning {
|
||||
color: $e2e-warning-color;
|
||||
}
|
||||
|
||||
.mx_E2EIcon_normal {
|
||||
color: var(--cpd-color-icon-tertiary);
|
||||
}
|
||||
|
||||
.mx_E2EIcon_verified,
|
||||
.mx_E2EIcon_warning {
|
||||
.mx_E2EIcon_normal::after {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_E2EIcon_verified {
|
||||
color: $e2e-verified-color;
|
||||
}
|
||||
|
||||
@@ -830,32 +830,11 @@ $left-gutter: 64px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: block;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
svg {
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
}
|
||||
|
||||
&.mx_EventTile_e2eIcon_warning::after {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
|
||||
background-color: $e2e-warning-color; /* red */
|
||||
}
|
||||
|
||||
&.mx_EventTile_e2eIcon_normal::after {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/info.svg");
|
||||
background-color: var(--cpd-color-icon-tertiary); /* grey */
|
||||
}
|
||||
|
||||
&.mx_EventTile_e2eIcon_decryption_failure::after {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
|
||||
background-color: var(--cpd-color-icon-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_EventTileBubble.mx_HistoryTile {
|
||||
margin: var(--EventTileBubble_margin-block) auto;
|
||||
|
||||
&::before {
|
||||
background-color: $header-panel-text-primary-color;
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/visibility-off.svg");
|
||||
svg {
|
||||
color: $header-panel-text-primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,6 @@ Please see LICENSE files in the repository root for full details.
|
||||
padding: 2px 0;
|
||||
font: var(--cpd-font-body-md-regular);
|
||||
|
||||
&.mx_ReplyTile_audio .mx_MFileBody_info_icon::before {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/volume-on-solid.svg");
|
||||
}
|
||||
|
||||
&.mx_ReplyTile_video .mx_MFileBody_info_icon::before {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/video-call-solid.svg");
|
||||
}
|
||||
|
||||
> a {
|
||||
display: grid;
|
||||
grid-template:
|
||||
|
||||
@@ -44,16 +44,11 @@ Please see LICENSE files in the repository root for full details.
|
||||
grid-column: 2;
|
||||
background-color: $accent;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
svg {
|
||||
display: inline-block;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
vertical-align: middle;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 20px;
|
||||
mask-position: center;
|
||||
background-color: #fff; /* on all themes */
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/voice-call-solid.svg");
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
padding: 10px;
|
||||
color: #fff; /* on all themes */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { type RefObject } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import type ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import ErrorBoundary from "../views/elements/ErrorBoundary";
|
||||
@@ -43,6 +44,7 @@ export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resize
|
||||
<div className="mx_RoomView_timeline">
|
||||
<ScrollPanel className="mx_RoomView_messagePanel">
|
||||
<EventTileBubble
|
||||
icon={<LockSolidIcon />}
|
||||
className="mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
title={_t("room|waiting_for_join_title", { brand })}
|
||||
subtitle={_t("room|waiting_for_join_subtitle", { brand })}
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { LinkIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { LinkIcon, OverflowHorizontalIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { type ButtonEvent } from "../elements/AccessibleButton";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
@@ -90,7 +90,9 @@ const ThreadListContextMenu: React.FC<ThreadListContextMenuProps> = ({
|
||||
isExpanded={menuDisplayed}
|
||||
ref={button}
|
||||
data-testid="threadlist-dropdown-button"
|
||||
/>
|
||||
>
|
||||
<OverflowHorizontalIcon />
|
||||
</ContextMenuTooltipButton>
|
||||
{menuDisplayed && (
|
||||
<IconizedContextMenu
|
||||
onFinished={closeThreadOptions}
|
||||
|
||||
@@ -10,9 +10,10 @@ import { Button, PasswordInput } from "@vector-im/compound-web";
|
||||
import LockSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid";
|
||||
import { debounce } from "lodash";
|
||||
import classNames from "classnames";
|
||||
import React, { type ChangeEvent, type FormEvent } from "react";
|
||||
import React, { type ChangeEvent, type FormEvent, type ReactNode } from "react";
|
||||
import { type SecretStorage } from "matrix-js-sdk/src/matrix";
|
||||
import { Flex } from "@element-hq/web-shared-components";
|
||||
import { ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { EncryptionCard } from "../../settings/encryption/EncryptionCard";
|
||||
@@ -142,27 +143,29 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
||||
};
|
||||
|
||||
private getRecoveryKeyFeedback(): React.ReactNode | null {
|
||||
let validationText: string;
|
||||
let content: ReactNode;
|
||||
let classes: string | undefined;
|
||||
|
||||
if (this.state.recoveryKeyCorrect) {
|
||||
// The recovery key is good. Empty feedback.
|
||||
validationText = "\xA0"; //
|
||||
content = "\xA0"; //
|
||||
} else if (this.state.recoveryKeyCorrect === null) {
|
||||
// The input element is empty. Tell the user they can also use a passphrase.
|
||||
validationText = _t("encryption|access_secret_storage_dialog|alternatives");
|
||||
content = _t("encryption|access_secret_storage_dialog|alternatives");
|
||||
} else {
|
||||
// The entered key is not (yet) correct. Tell them so.
|
||||
validationText = _t("encryption|access_secret_storage_dialog|key_validation_text|wrong_security_key");
|
||||
classes = classNames({
|
||||
"mx_AccessSecretStorageDialog_recoveryKeyFeedback": true,
|
||||
"mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid": true,
|
||||
});
|
||||
content = (
|
||||
<>
|
||||
<ErrorSolidIcon />
|
||||
{_t("encryption|access_secret_storage_dialog|key_validation_text|wrong_security_key")}
|
||||
</>
|
||||
);
|
||||
classes = "mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid";
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex align="center" className={classes}>
|
||||
{validationText}
|
||||
<Flex align="center" className={classNames("mx_AccessSecretStorageDialog_recoveryKeyFeedback", classes)}>
|
||||
{content}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ export type AccessibleButtonKind =
|
||||
| "primary_outline"
|
||||
| "primary_sm"
|
||||
| "secondary"
|
||||
| "secondary_content"
|
||||
| "content_inline"
|
||||
| "danger"
|
||||
| "danger_outline"
|
||||
@@ -39,8 +38,6 @@ export type AccessibleButtonKind =
|
||||
| "link"
|
||||
| "link_inline"
|
||||
| "link_sm"
|
||||
| "confirm_sm"
|
||||
| "cancel_sm"
|
||||
| "icon"
|
||||
| "icon_primary"
|
||||
| "icon_primary_outline";
|
||||
|
||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { type JSX, type Ref, type ReactNode } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { ErrorSolidIcon, LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import type { RoomEncryptionEventContent } from "matrix-js-sdk/src/types";
|
||||
import { _t } from "../../../languageHandler";
|
||||
@@ -53,6 +54,7 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
|
||||
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<LockSolidIcon />}
|
||||
className="mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
title={_t("common|encryption_enabled")}
|
||||
subtitle={subtitle}
|
||||
@@ -64,6 +66,7 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
|
||||
if (isRoomEncrypted) {
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<LockSolidIcon />}
|
||||
className="mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
title={_t("common|encryption_enabled")}
|
||||
subtitle={_t("timeline|m.room.encryption|disable_attempt")}
|
||||
@@ -74,7 +77,8 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
|
||||
|
||||
return (
|
||||
<EventTileBubble
|
||||
className="mx_cryptoEvent mx_cryptoEvent_icon mx_cryptoEvent_icon_warning"
|
||||
icon={<ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />}
|
||||
className="mx_cryptoEvent"
|
||||
title={_t("timeline|m.room.encryption|disabled")}
|
||||
subtitle={_t("timeline|m.room.encryption|unsupported")}
|
||||
ref={ref}
|
||||
|
||||
@@ -11,6 +11,7 @@ import classNames from "classnames";
|
||||
|
||||
interface IProps {
|
||||
className: string;
|
||||
icon: JSX.Element;
|
||||
title: string;
|
||||
timestamp?: JSX.Element;
|
||||
subtitle?: ReactNode;
|
||||
@@ -18,9 +19,10 @@ interface IProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const EventTileBubble = ({ className, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => {
|
||||
const EventTileBubble = ({ className, icon, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => {
|
||||
return (
|
||||
<div className={classNames("mx_EventTileBubble", className)} ref={ref}>
|
||||
{icon}
|
||||
<div className="mx_EventTileBubble_title">{title}</div>
|
||||
{subtitle && <div className="mx_EventTileBubble_subtitle">{subtitle}</div>}
|
||||
{children}
|
||||
|
||||
@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX } from "react";
|
||||
import { VisibilityOffIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { type IBodyProps } from "./IBodyProps";
|
||||
@@ -34,6 +35,7 @@ const HiddenBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
|
||||
|
||||
return (
|
||||
<span className="mx_HiddenBody" ref={ref}>
|
||||
<VisibilityOffIcon />
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -9,8 +9,14 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { type AllHTMLAttributes, createRef } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type MediaEventContent } from "matrix-js-sdk/src/types";
|
||||
import { MsgType } from "matrix-js-sdk/src/matrix";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { DownloadIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import {
|
||||
AttachmentIcon,
|
||||
DownloadIcon,
|
||||
VideoCallSolidIcon,
|
||||
VolumeOnSolidIcon,
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import Modal from "../../../Modal";
|
||||
@@ -190,9 +196,17 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
||||
|
||||
let placeholder: React.ReactNode = null;
|
||||
if (showGenericPlaceholder) {
|
||||
let icon = <AttachmentIcon />;
|
||||
// MFileBody is not generally used for Audio/Video but can be as part of ReplyTile
|
||||
if (this.content.msgtype === MsgType.Audio) {
|
||||
icon = <VolumeOnSolidIcon />;
|
||||
} else if (this.content.msgtype === MsgType.Video) {
|
||||
icon = <VideoCallSolidIcon />;
|
||||
}
|
||||
|
||||
placeholder = (
|
||||
<AccessibleButton className="mx_MediaBody mx_MFileBody_info" onClick={this.onPlaceholderClick}>
|
||||
<span className="mx_MFileBody_info_icon" />
|
||||
<span className="mx_MFileBody_info_icon">{icon}</span>
|
||||
<TextWithTooltip tooltip={presentableTextForFile(this.content, _t("common|attachment"), true)}>
|
||||
<span className="mx_MFileBody_info_filename">
|
||||
{presentableTextForFile(this.content, _t("common|attachment"), true, true)}
|
||||
|
||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { type JSX } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { VideoCallSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import WidgetStore from "../../../stores/WidgetStore";
|
||||
@@ -41,6 +42,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
|
||||
// removed
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<VideoCallSolidIcon />}
|
||||
className="mx_MJitsiWidgetEvent"
|
||||
title={_t("timeline|m.widget|jitsi_ended", { senderName })}
|
||||
timestamp={this.props.timestamp}
|
||||
@@ -50,6 +52,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
|
||||
// modified
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<VideoCallSolidIcon />}
|
||||
className="mx_MJitsiWidgetEvent"
|
||||
title={_t("timeline|m.widget|jitsi_updated", { senderName })}
|
||||
subtitle={joinCopy}
|
||||
@@ -60,6 +63,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
|
||||
// assume added
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<VideoCallSolidIcon />}
|
||||
className="mx_MJitsiWidgetEvent"
|
||||
title={_t("timeline|m.widget|jitsi_started", { senderName })}
|
||||
subtitle={joinCopy}
|
||||
|
||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { type JSX } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { getNameForEventRoom, userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver";
|
||||
@@ -73,6 +74,7 @@ const MKeyVerificationRequest: React.FC<Props> = ({ mxEvent, timestamp }) => {
|
||||
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<LockSolidIcon />}
|
||||
className="mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
|
||||
@@ -11,6 +11,7 @@ import classNames from "classnames";
|
||||
import { type MatrixEvent, MatrixEventEvent, type Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { uniqBy } from "lodash";
|
||||
import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue";
|
||||
import { ReactionAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { isContentActionable } from "../../../utils/EventUtils";
|
||||
@@ -54,7 +55,9 @@ const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => {
|
||||
}}
|
||||
isExpanded={menuDisplayed}
|
||||
ref={button}
|
||||
/>
|
||||
>
|
||||
<ReactionAddIcon />
|
||||
</ContextMenuTooltipButton>
|
||||
|
||||
{contextMenu}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { useContext, type JSX } from "react";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { DeleteIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
@@ -34,6 +35,7 @@ const RedactedBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
|
||||
|
||||
return (
|
||||
<span className="mx_RedactedBody" ref={ref} title={titleText}>
|
||||
<DeleteIcon />
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { type JSX, useCallback } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type MatrixEvent, type Room, type RoomState } from "matrix-js-sdk/src/matrix";
|
||||
import { ChatSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
@@ -89,6 +90,7 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
|
||||
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<ChatSolidIcon />}
|
||||
className="mx_CreateEvent"
|
||||
title={_t("timeline|m.room.create|continuation")}
|
||||
timestamp={timestamp}
|
||||
@@ -128,6 +130,7 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
|
||||
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<ChatSolidIcon />}
|
||||
className="mx_CreateEvent"
|
||||
title={_t("timeline|m.room.create|continuation")}
|
||||
subtitle={link}
|
||||
|
||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { type JSX, useContext, useEffect } from "react";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { OverflowHorizontalIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import BaseCard from "./BaseCard";
|
||||
@@ -73,7 +74,9 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
|
||||
onClick={openMenu}
|
||||
isExpanded={menuDisplayed}
|
||||
label={_t("common|options")}
|
||||
/>
|
||||
>
|
||||
<OverflowHorizontalIcon />
|
||||
</ContextMenuButton>
|
||||
{contextMenu}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -38,9 +38,9 @@ interface Props {
|
||||
}
|
||||
|
||||
const icons: Record<E2EStatus, JSX.Element> = {
|
||||
[E2EStatus.Warning]: <ErrorSolidIcon className="mx_E2EIcon_warning" />,
|
||||
[E2EStatus.Normal]: <LockSolidIcon className="mx_E2EIcon_normal" />,
|
||||
[E2EStatus.Verified]: <ShieldIcon className="mx_E2EIcon_verified" />,
|
||||
[E2EStatus.Warning]: <ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />,
|
||||
[E2EStatus.Normal]: <LockSolidIcon color="var(--cpd-color-icon-tertiary)" />,
|
||||
[E2EStatus.Verified]: <ShieldIcon color="var(--cpd-color-icon-success-primary)" />,
|
||||
};
|
||||
|
||||
const E2EIcon: React.FC<Props> = ({ isUser, status, className, size, onClick, hideTooltip, tooltipPlacement }) => {
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
} from "matrix-js-sdk/src/crypto-api";
|
||||
import { Tooltip } from "@vector-im/compound-web";
|
||||
import { uniqueId } from "lodash";
|
||||
import { ErrorSolidIcon, InfoIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import ReplyChain from "../elements/ReplyChain";
|
||||
import { _t } from "../../../languageHandler";
|
||||
@@ -1518,13 +1519,13 @@ function E2ePadlockDecryptionFailure(props: Omit<IE2ePadlockProps, "title" | "ic
|
||||
}
|
||||
|
||||
enum E2ePadlockIcon {
|
||||
/** grey shield */
|
||||
/** Compound Info icon in grey */
|
||||
Normal = "normal",
|
||||
|
||||
/** red shield with (!) */
|
||||
/** Compound ErrorSolid icon in red */
|
||||
Warning = "warning",
|
||||
|
||||
/** key in grey circle */
|
||||
/** Compound ErrorSolid icon in grey */
|
||||
DecryptionFailure = "decryption_failure",
|
||||
}
|
||||
|
||||
@@ -1534,6 +1535,12 @@ interface IE2ePadlockProps {
|
||||
}
|
||||
|
||||
class E2ePadlock extends React.Component<IE2ePadlockProps> {
|
||||
private static icons: Record<E2ePadlockIcon, JSX.Element> = {
|
||||
[E2ePadlockIcon.Normal]: <InfoIcon color="var(--cpd-color-icon-tertiary)" />,
|
||||
[E2ePadlockIcon.Warning]: <ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />,
|
||||
[E2ePadlockIcon.DecryptionFailure]: <ErrorSolidIcon color="var(--cpd-color-icon-tertiary)" />,
|
||||
};
|
||||
|
||||
public constructor(props: IE2ePadlockProps) {
|
||||
super(props);
|
||||
|
||||
@@ -1543,12 +1550,13 @@ class E2ePadlock extends React.Component<IE2ePadlockProps> {
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
const classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`;
|
||||
// We specify isTriggerInteractive=true and make the div interactive manually as a workaround for
|
||||
// https://github.com/element-hq/compound/issues/294
|
||||
return (
|
||||
<Tooltip label={this.props.title} isTriggerInteractive={true}>
|
||||
<div className={classes} tabIndex={0} aria-label={_t("timeline|e2e_state")} />
|
||||
<div className="mx_EventTile_e2eIcon" tabIndex={0} aria-label={_t("timeline|e2e_state")}>
|
||||
{E2ePadlock.icons[this.props.icon]}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
import { EventTimeline } from "matrix-js-sdk/src/matrix";
|
||||
import { VisibilityOffIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import EventTileBubble from "../messages/EventTileBubble";
|
||||
import { _t } from "../../../languageHandler";
|
||||
@@ -28,6 +29,7 @@ const HistoryTile: React.FC = () => {
|
||||
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<VisibilityOffIcon />}
|
||||
className="mx_HistoryTile"
|
||||
title={_t("timeline|historical_messages_unavailable")}
|
||||
subtitle={subtitle}
|
||||
|
||||
@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { type JSX, useContext } from "react";
|
||||
import { EventType, type Room, type User, type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
@@ -291,7 +292,8 @@ const NewRoomIntro: React.FC = () => {
|
||||
<li className="mx_NewRoomIntro">
|
||||
{!hasExpectedEncryptionSettings(cli, room) && (
|
||||
<EventTileBubble
|
||||
className="mx_cryptoEvent mx_cryptoEvent_icon_warning"
|
||||
icon={<ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />}
|
||||
className="mx_cryptoEvent"
|
||||
title={_t("room|intro|unencrypted_warning")}
|
||||
subtitle={subtitle}
|
||||
/>
|
||||
|
||||
@@ -112,8 +112,6 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
||||
const classes = classNames("mx_ReplyTile", {
|
||||
mx_ReplyTile_inline: msgType === MsgType.Emote,
|
||||
mx_ReplyTile_info: isInfoMessage && !mxEvent.isRedacted(),
|
||||
mx_ReplyTile_audio: msgType === MsgType.Audio,
|
||||
mx_ReplyTile_video: msgType === MsgType.Video,
|
||||
});
|
||||
|
||||
let permalink = "#";
|
||||
|
||||
@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX } from "react";
|
||||
import { VoiceCallSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
|
||||
import { _t } from "../../../languageHandler";
|
||||
@@ -59,7 +60,9 @@ class DialPadButton extends React.PureComponent<DigitButtonProps | DialButtonPro
|
||||
className="mx_DialPad_button mx_DialPad_dialButton"
|
||||
onClick={this.onClick}
|
||||
aria-label={_t("voip|dial")}
|
||||
/>
|
||||
>
|
||||
<VoiceCallSolidIcon />
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,12 +594,14 @@ describe("RoomView", () => {
|
||||
|
||||
const { container } = await renderRoomView();
|
||||
// We no longer show the grey shield for encrypted rooms, so it should not be there.
|
||||
await waitFor(() => expect(container.querySelector(".mx_E2EIcon_normal")).not.toBeInTheDocument());
|
||||
await waitFor(() => expect(container.querySelector(".mx_E2EIcon")).not.toBeInTheDocument());
|
||||
|
||||
const verificationStatus = new UserVerificationStatus(true, true, false);
|
||||
jest.spyOn(cli.getCrypto()!, "getUserVerificationStatus").mockResolvedValue(verificationStatus);
|
||||
cli.emit(CryptoEvent.UserTrustStatusChanged, cli.getSafeUserId(), verificationStatus);
|
||||
await waitFor(() => expect(container.querySelector(".mx_E2EIcon_verified")).toBeInTheDocument());
|
||||
await waitFor(() =>
|
||||
expect(container.querySelector(".mx_E2EIcon")).toHaveAccessibleName("Everyone in this room is verified"),
|
||||
);
|
||||
});
|
||||
|
||||
describe("video rooms", () => {
|
||||
|
||||
@@ -63,6 +63,17 @@ exports[`MessagePanel should handle lots of membership events quickly 1`] = `
|
||||
<div
|
||||
class="mx_EventTileBubble mx_HistoryTile"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m16.1 13.3-1.45-1.45q.225-1.175-.675-2.2t-2.325-.8L10.2 7.4q.424-.2.863-.3A4.2 4.2 0 0 1 12 7q1.875 0 3.188 1.312Q16.5 9.625 16.5 11.5q0 .5-.1.938t-.3.862m3.2 3.15-1.45-1.4a11 11 0 0 0 1.688-1.588A9 9 0 0 0 20.8 11.5q-1.25-2.524-3.588-4.013Q14.875 6 12 6q-.724 0-1.425.1a10 10 0 0 0-1.375.3L7.65 4.85A11.1 11.1 0 0 1 12 4q3.575 0 6.425 1.887T22.7 10.8a.8.8 0 0 1 .1.313q.025.188.025.387a2 2 0 0 1-.125.7 10.9 10.9 0 0 1-3.4 4.25m-.2 5.45-3.5-3.45q-.874.274-1.762.413Q12.95 19 12 19q-3.575 0-6.425-1.887T1.3 12.2a.8.8 0 0 1-.1-.312 3 3 0 0 1 0-.763.8.8 0 0 1 .1-.3Q1.825 9.7 2.55 8.75A13.3 13.3 0 0 1 4.15 7L2.075 4.9a.93.93 0 0 1-.275-.688q0-.412.3-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275l17 17q.275.275.288.688a.93.93 0 0 1-.288.712.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275M5.55 8.4q-.725.65-1.325 1.425A9 9 0 0 0 3.2 11.5q1.25 2.524 3.588 4.012T12 17q.5 0 .975-.062.475-.063.975-.138l-.9-.95q-.274.075-.525.113A3.5 3.5 0 0 1 12 16q-1.875 0-3.187-1.312Q7.5 13.375 7.5 11.5q0-.274.038-.525.037-.25.112-.525z"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_EventTileBubble_title"
|
||||
>
|
||||
|
||||
@@ -141,8 +141,20 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
||||
class="mx_NewRoomIntro"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning"
|
||||
class="mx_EventTileBubble mx_cryptoEvent"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-critical-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_EventTileBubble_title"
|
||||
>
|
||||
@@ -311,8 +323,20 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
||||
class="mx_NewRoomIntro"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning"
|
||||
class="mx_EventTileBubble mx_cryptoEvent"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-critical-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_EventTileBubble_title"
|
||||
>
|
||||
@@ -676,6 +700,17 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
||||
<div
|
||||
class="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412A1.93 1.93 0 0 1 6 8h1V6q0-2.075 1.463-3.537Q9.926 1 12 1q2.075 0 3.537 1.463Q17 3.925 17 6v2h1q.824 0 1.413.588Q20 9.175 20 10v10q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zM9 8h6V6q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 3q-1.25 0-2.125.875A2.9 2.9 0 0 0 9 6z"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_EventTileBubble_title"
|
||||
>
|
||||
@@ -2974,7 +3009,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
|
||||
style="width: 12px; height: 12px;"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_verified"
|
||||
color="var(--cpd-color-icon-success-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
||||
@@ -27,7 +27,7 @@ exports[`<UntrustedDeviceDialog /> should display the dialog for the device of a
|
||||
style="width: 24px; height: 24px;"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_warning"
|
||||
color="var(--cpd-color-icon-critical-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -116,7 +116,7 @@ exports[`<UntrustedDeviceDialog /> should display the dialog for the device of t
|
||||
style="width: 24px; height: 24px;"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_warning"
|
||||
color="var(--cpd-color-icon-critical-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
||||
@@ -37,7 +37,7 @@ exports[`VerificationRequestDialog After scanning QR, shows confirmation dialog
|
||||
style="width: 128px; height: 128px;"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_verified"
|
||||
color="var(--cpd-color-icon-success-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -313,7 +313,7 @@ exports[`VerificationRequestDialog Shows a successful message if verification fi
|
||||
style="width: 128px; height: 128px;"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_verified"
|
||||
color="var(--cpd-color-icon-success-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
||||
@@ -73,7 +73,7 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
|
||||
data-testid="e2e-icon"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_normal"
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -237,7 +237,7 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
|
||||
data-testid="e2e-icon"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_warning"
|
||||
color="var(--cpd-color-icon-critical-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -401,7 +401,7 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
|
||||
data-testid="e2e-icon"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_verified"
|
||||
color="var(--cpd-color-icon-success-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -555,7 +555,7 @@ exports[`<Users /> should render a single user 1`] = `
|
||||
data-testid="e2e-icon"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_verified"
|
||||
color="var(--cpd-color-icon-success-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -587,7 +587,7 @@ exports[`<Users /> should render a single user 1`] = `
|
||||
data-testid="e2e-icon"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_verified"
|
||||
color="var(--cpd-color-icon-success-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -613,7 +613,7 @@ exports[`<Users /> should render a single user 1`] = `
|
||||
data-testid="e2e-icon"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_normal"
|
||||
color="var(--cpd-color-icon-tertiary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -637,7 +637,7 @@ exports[`<Users /> should render a single user 1`] = `
|
||||
data-testid="e2e-icon"
|
||||
>
|
||||
<svg
|
||||
class="mx_E2EIcon_warning"
|
||||
color="var(--cpd-color-icon-critical-primary)"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
||||
@@ -28,7 +28,19 @@ exports[`AppTile destroys non-persisted right panel widget on room change 1`] =
|
||||
class="mx_AccessibleButton mx_BaseCard_header_title_button--option"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby="_r_0_"
|
||||
|
||||
@@ -85,4 +85,31 @@ describe("<MFileBody/>", () => {
|
||||
expect(getByRole("link", { name: "Download" })).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it.each(["m.file", "m.audio", "m.video"])("should show %s generic placeholder", async (msgtype) => {
|
||||
const mediaEvent = new MatrixEvent({
|
||||
room_id: "!room:server",
|
||||
sender: userId,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
body: "alt",
|
||||
msgtype,
|
||||
url: "mxc://server/image",
|
||||
},
|
||||
});
|
||||
|
||||
const { container, getByRole } = render(
|
||||
<ScopedRoomContextProvider {...({ timelineRenderingType: TimelineRenderingType.File } as any)}>
|
||||
<MFileBody
|
||||
{...props}
|
||||
mxEvent={mediaEvent}
|
||||
mediaEventHelper={new MediaEventHelper(mediaEvent)}
|
||||
showGenericPlaceholder={true}
|
||||
/>
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
|
||||
expect(getByRole("button", { name: "alt" })).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,3 +35,126 @@ exports[`<MFileBody/> should show a download button in file rendering type 1`] =
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<MFileBody/> should show m.audio generic placeholder 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_MFileBody"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_MediaBody mx_MFileBody_info"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_MFileBody_info_icon"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3 14v-4a2 2 0 0 1 2-2h2l3.293-3.293c.63-.63 1.707-.184 1.707.707v13.172c0 .89-1.077 1.337-1.707.707L7 16H5a2 2 0 0 1-2-2m11.122-5.536a1 1 0 0 1 1.414 0A5 5 0 0 1 17 12c0 1.38-.56 2.632-1.464 3.536a1 1 0 0 1-1.415-1.415 3 3 0 0 0 .88-2.121c0-.829-.335-1.577-.88-2.121a1 1 0 0 1 0-1.415"
|
||||
/>
|
||||
<path
|
||||
d="M16.95 5.636a1 1 0 0 1 1.414 0A8.98 8.98 0 0 1 21 12a8.98 8.98 0 0 1-2.636 6.364 1 1 0 0 1-1.414-1.414A6.98 6.98 0 0 0 19 12a6.98 6.98 0 0 0-2.05-4.95 1 1 0 0 1 0-1.414"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
aria-labelledby="_r_6_"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_MFileBody_info_filename"
|
||||
>
|
||||
alt
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<MFileBody/> should show m.file generic placeholder 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_MFileBody"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_MediaBody mx_MFileBody_info"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_MFileBody_info_icon"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.5 22q-2.3 0-3.9-1.6T6 16.5V6q0-1.65 1.175-2.825T10 2t2.825 1.175T14 6v9.5q0 1.05-.725 1.775T11.5 18t-1.775-.725T9 15.5V6.75A.73.73 0 0 1 9.75 6a.73.73 0 0 1 .75.75v8.75q0 .424.287.712.288.288.713.288.424 0 .713-.288a.97.97 0 0 0 .287-.712V6q0-1.05-.725-1.775T10 3.5t-1.775.725T7.5 6v10.5q0 1.65 1.175 2.825T11.5 20.5t2.825-1.175T15.5 16.5V6.75a.73.73 0 0 1 .75-.75.73.73 0 0 1 .75.75v9.75q0 2.3-1.6 3.9T11.5 22"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
aria-labelledby="_r_0_"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_MFileBody_info_filename"
|
||||
>
|
||||
alt
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<MFileBody/> should show m.video generic placeholder 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_MFileBody"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_MediaBody mx_MFileBody_info"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_MFileBody_info_icon"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
aria-labelledby="_r_c_"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_MFileBody_info_filename"
|
||||
>
|
||||
alt
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -172,7 +172,19 @@ exports[`<MImageBody/> should render MFileBody for svg with no thumbnail 1`] = `
|
||||
>
|
||||
<span
|
||||
class="mx_MFileBody_info_icon"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.5 22q-2.3 0-3.9-1.6T6 16.5V6q0-1.65 1.175-2.825T10 2t2.825 1.175T14 6v9.5q0 1.05-.725 1.775T11.5 18t-1.775-.725T9 15.5V6.75A.73.73 0 0 1 9.75 6a.73.73 0 0 1 .75.75v8.75q0 .424.287.712.288.288.713.288.424 0 .713-.288a.97.97 0 0 0 .287-.712V6q0-1.05-.725-1.775T10 3.5t-1.775.725T7.5 6v10.5q0 1.65 1.175 2.825T11.5 20.5t2.825-1.175T15.5 16.5V6.75a.73.73 0 0 1 .75-.75.73.73 0 0 1 .75.75v9.75q0 2.3-1.6 3.9T11.5 22"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
aria-labelledby="_r_0_"
|
||||
tabindex="0"
|
||||
|
||||
@@ -5,6 +5,17 @@ exports[`<RoomPredecessorTile /> Renders as expected 1`] = `
|
||||
<div
|
||||
class="mx_EventTileBubble mx_CreateEvent"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2.95 16.3 1.5 21.25a.94.94 0 0 0 .25 1 .94.94 0 0 0 1 .25l4.95-1.45a10.2 10.2 0 0 0 2.1.712Q10.875 22 12 22a9.7 9.7 0 0 0 3.9-.788 10.1 10.1 0 0 0 3.175-2.137q1.35-1.35 2.137-3.175A9.7 9.7 0 0 0 22 12a9.7 9.7 0 0 0-.788-3.9 10.1 10.1 0 0 0-2.137-3.175q-1.35-1.35-3.175-2.137A9.7 9.7 0 0 0 12 2a9.7 9.7 0 0 0-3.9.788 10.1 10.1 0 0 0-3.175 2.137Q3.575 6.275 2.788 8.1A9.7 9.7 0 0 0 2 12q0 1.125.238 2.2.237 1.076.712 2.1"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_EventTileBubble_title"
|
||||
>
|
||||
|
||||
@@ -269,8 +269,8 @@ describe("EventTile", () => {
|
||||
|
||||
// there should be a warning shield
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1);
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain(
|
||||
"mx_EventTile_e2eIcon_warning",
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName(
|
||||
"Encrypted by a device not verified by its owner.",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -298,10 +298,13 @@ describe("EventTile", () => {
|
||||
|
||||
it.each([
|
||||
[EventShieldReason.UNKNOWN, "Unknown error"],
|
||||
[EventShieldReason.UNVERIFIED_IDENTITY, "unverified user"],
|
||||
[EventShieldReason.UNSIGNED_DEVICE, "device not verified by its owner"],
|
||||
[EventShieldReason.UNKNOWN_DEVICE, "unknown or deleted device"],
|
||||
[EventShieldReason.AUTHENTICITY_NOT_GUARANTEED, "can't be guaranteed"],
|
||||
[EventShieldReason.UNVERIFIED_IDENTITY, "Encrypted by an unverified user."],
|
||||
[EventShieldReason.UNSIGNED_DEVICE, "Encrypted by a device not verified by its owner."],
|
||||
[EventShieldReason.UNKNOWN_DEVICE, "Encrypted by an unknown or deleted device."],
|
||||
[
|
||||
EventShieldReason.AUTHENTICITY_NOT_GUARANTEED,
|
||||
"The authenticity of this encrypted message can't be guaranteed on this device.",
|
||||
],
|
||||
[EventShieldReason.MISMATCHED_SENDER_KEY, "Encrypted by an unverified session"],
|
||||
[EventShieldReason.SENT_IN_CLEAR, "Not encrypted"],
|
||||
[EventShieldReason.VERIFICATION_VIOLATION, "Sender's verified identity was reset"],
|
||||
@@ -326,12 +329,7 @@ describe("EventTile", () => {
|
||||
|
||||
const e2eIcons = container.getElementsByClassName("mx_EventTile_e2eIcon");
|
||||
expect(e2eIcons).toHaveLength(1);
|
||||
expect(e2eIcons[0].classList).toContain("mx_EventTile_e2eIcon_normal");
|
||||
fireEvent.focus(e2eIcons[0]);
|
||||
expect(e2eIcons[0].getAttribute("aria-labelledby")).toBeTruthy();
|
||||
expect(document.getElementById(e2eIcons[0].getAttribute("aria-labelledby")!)).toHaveTextContent(
|
||||
expectedText,
|
||||
);
|
||||
expect(e2eIcons[0]).toHaveAccessibleName(expectedText);
|
||||
});
|
||||
|
||||
describe("undecryptable event", () => {
|
||||
@@ -360,8 +358,8 @@ describe("EventTile", () => {
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1);
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain(
|
||||
"mx_EventTile_e2eIcon_decryption_failure",
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName(
|
||||
"This message could not be decrypted",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -436,8 +434,8 @@ describe("EventTile", () => {
|
||||
|
||||
// check it was updated
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1);
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain(
|
||||
"mx_EventTile_e2eIcon_warning",
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName(
|
||||
"Encrypted by a device not verified by its owner.",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -481,9 +479,7 @@ describe("EventTile", () => {
|
||||
|
||||
// check it was updated
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1);
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain(
|
||||
"mx_EventTile_e2eIcon_warning",
|
||||
);
|
||||
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName("Not encrypted");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -330,10 +330,10 @@ describe("SendWysiwygComposer", () => {
|
||||
"Left icon when %s",
|
||||
({ isRichTextEnabled }) => {
|
||||
it.each([
|
||||
[E2EStatus.Verified, "mx_E2EIcon_verified"],
|
||||
[E2EStatus.Warning, "mx_E2EIcon_warning"],
|
||||
[E2EStatus.Verified, "Everyone in this room is verified"],
|
||||
[E2EStatus.Warning, "Someone is using an unknown session"],
|
||||
[undefined, undefined],
|
||||
])("Should render left icon when e2eStatus is %s", async (e2eStatus, expectedClass) => {
|
||||
])("Should render left icon when e2eStatus is %s", async (e2eStatus, expectedLabel) => {
|
||||
// When
|
||||
customRender(jest.fn(), jest.fn(), false, isRichTextEnabled, undefined, e2eStatus);
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true"));
|
||||
@@ -341,9 +341,9 @@ describe("SendWysiwygComposer", () => {
|
||||
// Then
|
||||
expect(leftIcon).toBeInTheDocument();
|
||||
expect(leftIcon).toHaveClass("mx_E2EIcon");
|
||||
if (expectedClass) {
|
||||
if (expectedLabel) {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(leftIcon.querySelector("svg")).toHaveClass(expectedClass);
|
||||
expect(leftIcon).toHaveAccessibleName(expectedLabel);
|
||||
} else {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(leftIcon.querySelector("svg")).not.toBeInTheDocument();
|
||||
|
||||