diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index bb802ef03e..ad1804bfc5 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -16,6 +16,7 @@
/src/components/views/dialogs/devtools/Crypto.tsx @element-hq/element-crypto-web-reviewers
/playwright/e2e/crypto/ @element-hq/element-crypto-web-reviewers
/playwright/e2e/settings/encryption-user-tab/ @element-hq/element-crypto-web-reviewers
+/packages/shared-components/src/crypto/ @element-hq/element-crypto-web-reviewers
/src/models/Call.ts @element-hq/element-call-reviewers
diff --git a/package.json b/package.json
index 2b8c1e243f..92cf154988 100644
--- a/package.json
+++ b/package.json
@@ -90,7 +90,6 @@
"@matrix-org/analytics-events": "^0.31.0",
"@matrix-org/emojibase-bindings": "^1.5.0",
"@matrix-org/react-sdk-module-api": "^2.4.0",
- "@matrix-org/spec": "^1.7.0",
"@sentry/browser": "^10.0.0",
"@types/png-chunks-extract": "^1.0.2",
"@vector-im/compound-design-tokens": "6.9.0",
diff --git a/packages/shared-components/__vis__/linux/__baselines__/crypto/SasEmoji/SasEmoji.stories.tsx/default-auto.png b/packages/shared-components/__vis__/linux/__baselines__/crypto/SasEmoji/SasEmoji.stories.tsx/default-auto.png
new file mode 100644
index 0000000000..8e27242e47
Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/crypto/SasEmoji/SasEmoji.stories.tsx/default-auto.png differ
diff --git a/packages/shared-components/__vis__/linux/__baselines__/crypto/SasEmoji/SasEmoji.stories.tsx/worst-case-albanian-auto.png b/packages/shared-components/__vis__/linux/__baselines__/crypto/SasEmoji/SasEmoji.stories.tsx/worst-case-albanian-auto.png
new file mode 100644
index 0000000000..c845bdeabe
Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/crypto/SasEmoji/SasEmoji.stories.tsx/worst-case-albanian-auto.png differ
diff --git a/packages/shared-components/__vis__/linux/__baselines__/crypto/SasEmoji/SasEmoji.stories.tsx/worst-case-german-auto.png b/packages/shared-components/__vis__/linux/__baselines__/crypto/SasEmoji/SasEmoji.stories.tsx/worst-case-german-auto.png
new file mode 100644
index 0000000000..6565cf86b7
Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/crypto/SasEmoji/SasEmoji.stories.tsx/worst-case-german-auto.png differ
diff --git a/packages/shared-components/package.json b/packages/shared-components/package.json
index 5987d81dbd..0ed297872e 100644
--- a/packages/shared-components/package.json
+++ b/packages/shared-components/package.json
@@ -50,6 +50,7 @@
},
"dependencies": {
"@element-hq/element-web-module-api": "^1.8.0",
+ "@matrix-org/spec": "^1.7.0",
"@vector-im/compound-design-tokens": "^6.4.3",
"classnames": "^2.5.1",
"counterpart": "^0.18.6",
diff --git a/packages/shared-components/src/crypto/SasEmoji/SasEmoji.module.css b/packages/shared-components/src/crypto/SasEmoji/SasEmoji.module.css
new file mode 100644
index 0000000000..b763ef6664
--- /dev/null
+++ b/packages/shared-components/src/crypto/SasEmoji/SasEmoji.module.css
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2026 Element Creations Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+.container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--cpd-space-2x);
+ justify-content: space-evenly;
+}
+
+.segment {
+ display: inline-block;
+ margin-bottom: var(--cpd-space-4x);
+ text-align: center;
+ /* Allow maximum 4 per line, accounting for 8px gap */
+ min-width: calc(25% - 8px);
+}
+
+.emoji {
+ /* Use the Twemoji font for consistency with other clients */
+ font-family: Twemoji, var(--cpd-font-family-sans);
+ font-size: var(--cpd-font-size-heading-xl);
+}
+
+.label {
+ font-weight: var(--cpd-font-weight-regular);
+ font-size: var(--cpd-font-size-body-lg);
+ color: var(--cpd-color-text-secondary);
+}
diff --git a/packages/shared-components/src/crypto/SasEmoji/SasEmoji.stories.tsx b/packages/shared-components/src/crypto/SasEmoji/SasEmoji.stories.tsx
new file mode 100644
index 0000000000..9c0fcc3f40
--- /dev/null
+++ b/packages/shared-components/src/crypto/SasEmoji/SasEmoji.stories.tsx
@@ -0,0 +1,48 @@
+/*
+Copyright 2026 Element Creations Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { type Meta, type StoryObj } from "@storybook/react-vite";
+
+import { SasEmoji } from "./SasEmoji";
+
+const meta = {
+ title: "Crypto/SasEmoji",
+ component: SasEmoji,
+ tags: ["autodocs"],
+ args: {
+ emoji: ["🍕", "🌽", "🚀", "🔒", "🔧", "🍓", "⌛"],
+ },
+ parameters: {
+ design: {
+ type: "figma",
+ url: "https://www.figma.com/design/XLWIAB5n8yObYvU0INKPK1/Verification-by-Emoji?node-id=1-2935&t=NrV9JnuItrAyyh53-4",
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const WorstCaseAlbanian: Story = {
+ globals: {
+ language: "sq",
+ },
+ args: {
+ emoji: ["🎅", "🎅", "🎅", "🎅", "🎅", "🎅", "🎅"],
+ },
+};
+
+export const WorstCaseGerman: Story = {
+ globals: {
+ language: "de",
+ },
+ args: {
+ emoji: ["🔧", "🔧", "🔧", "🔧", "🔧", "🔧", "🔧"],
+ },
+};
diff --git a/packages/shared-components/src/crypto/SasEmoji/SasEmoji.test.tsx b/packages/shared-components/src/crypto/SasEmoji/SasEmoji.test.tsx
new file mode 100644
index 0000000000..c9946d52a0
--- /dev/null
+++ b/packages/shared-components/src/crypto/SasEmoji/SasEmoji.test.tsx
@@ -0,0 +1,19 @@
+/*
+Copyright 2026 Element Creations Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import React from "react";
+import { describe, it, expect } from "vitest";
+import { render } from "@test-utils";
+
+import { SasEmoji } from "./SasEmoji";
+
+describe("", () => {
+ it("should match snapshot", () => {
+ const { asFragment } = render();
+ expect(asFragment()).toMatchSnapshot();
+ });
+});
diff --git a/packages/shared-components/src/crypto/SasEmoji/SasEmoji.tsx b/packages/shared-components/src/crypto/SasEmoji/SasEmoji.tsx
new file mode 100644
index 0000000000..6701cb37c9
--- /dev/null
+++ b/packages/shared-components/src/crypto/SasEmoji/SasEmoji.tsx
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2026 Element Creations Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import React, { type JSX } from "react";
+import classNames from "classnames";
+
+import { type SasEmoji, tEmoji } from "./SasEmojiTranslate.ts";
+import styles from "./SasEmoji.module.css";
+import { useI18n } from "../../utils/i18nContext.ts";
+
+export type Props = {
+ /**
+ * The emoji to render
+ */
+ emoji: [SasEmoji, SasEmoji, SasEmoji, SasEmoji, SasEmoji, SasEmoji, SasEmoji];
+ /**
+ * Optional className to apply to the container
+ */
+ className?: string;
+};
+
+/**
+ * Renders the 7 emoji used for SAS verification.
+ * The component is responsive so can be rendered in any context, dialog, side panel.
+ */
+export function SasEmoji({ emoji, className }: Props): JSX.Element {
+ const { language } = useI18n();
+
+ const emojiBlocks = emoji.map((emoji, i) => (
+
+
+ {emoji}
+
+
{tEmoji(emoji, language)}
+
+ ));
+
+ return {emojiBlocks}
;
+}
diff --git a/packages/shared-components/src/crypto/SasEmoji/SasEmojiTranslate.test.ts b/packages/shared-components/src/crypto/SasEmoji/SasEmojiTranslate.test.ts
new file mode 100644
index 0000000000..8251e52ea4
--- /dev/null
+++ b/packages/shared-components/src/crypto/SasEmoji/SasEmojiTranslate.test.ts
@@ -0,0 +1,26 @@
+/*
+Copyright 2026 Element Creations Ltd.
+
+SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
+Please see LICENSE files in the repository root for full details.
+*/
+
+import { describe, it, expect } from "vitest";
+
+import { tEmoji, type SasEmoji } from "./SasEmojiTranslate.ts";
+
+describe("tEmoji", () => {
+ it.each([
+ ["🐶", "en-GB", "Dog"],
+ ["🐶", "en", "Dog"],
+ ["🐶", "de-DE", "Hund"],
+ ["🐶", "pt", "Cachorro"],
+ ["🔧", "de-DE", "Schraubenschlüssel"],
+ ["🎅", "sq", "Babagjyshi i Vitit të Ri"],
+ ] as [emoji: SasEmoji, locale: string, expectation: string][])(
+ "should handle locale %s",
+ (emoji, locale, expectation) => {
+ expect(tEmoji(emoji, locale)).toEqual(expectation);
+ },
+ );
+});
diff --git a/packages/shared-components/src/crypto/SasEmoji/SasEmojiTranslate.ts b/packages/shared-components/src/crypto/SasEmoji/SasEmojiTranslate.ts
new file mode 100644
index 0000000000..da977a98e0
--- /dev/null
+++ b/packages/shared-components/src/crypto/SasEmoji/SasEmojiTranslate.ts
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2026 Element Creations Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+import SasEmojiJson from "@matrix-org/spec/sas-emoji.json";
+import { getNormalizedLanguageKeys } from "matrix-web-i18n";
+
+// Type as specified in https://spec.matrix.org/v1.17/client-server-api/#sas-method-emoji
+export type SasEmoji =
+ | "🐶"
+ | "🐱"
+ | "🦁"
+ | "🐎"
+ | "🦄"
+ | "🐷"
+ | "🐘"
+ | "🐰"
+ | "🐼"
+ | "🐓"
+ | "🐧"
+ | "🐢"
+ | "🐟"
+ | "🐙"
+ | "🦋"
+ | "🌷"
+ | "🌳"
+ | "🌵"
+ | "🍄"
+ | "🌏"
+ | "🌙"
+ | "☁"
+ | "🔥"
+ | "🍌"
+ | "🍎"
+ | "🍓"
+ | "🌽"
+ | "🍕"
+ | "🎂"
+ | "❤"
+ | "😀"
+ | "🤖"
+ | "🎩"
+ | "👓"
+ | "🔧"
+ | "🎅"
+ | "👍"
+ | "☂"
+ | "⌛"
+ | "⏰"
+ | "🎁"
+ | "💡"
+ | "📕"
+ | "✏"
+ | "📎"
+ | "✂"
+ | "🔒"
+ | "🔑"
+ | "🔨"
+ | "☎"
+ | "🏁"
+ | "🚂"
+ | "🚲"
+ | "✈"
+ | "🚀"
+ | "🏆"
+ | "⚽"
+ | "🎸"
+ | "🎺"
+ | "🔔"
+ | "⚓"
+ | "🎧"
+ | "📁"
+ | "📌";
+
+const SasEmojiMap = new Map<
+ SasEmoji,
+ [
+ description: string,
+ translations: {
+ [normalizedLanguageKey: string]: string;
+ },
+ ]
+>(
+ SasEmojiJson.map(({ emoji, description, translated_descriptions: translations }) => [
+ emoji as SasEmoji,
+ [
+ description,
+ // Normalize the translation keys
+ Object.keys(translations).reduce>((o, k) => {
+ for (const key of getNormalizedLanguageKeys(k)) {
+ o[key] = translations[k as keyof typeof translations]!;
+ }
+ return o;
+ }, {}),
+ ],
+ ]),
+);
+
+/**
+ * Translate given SAS emoji into the target locale
+ * @param emoji - the SAS emoji to translate
+ * @param locale - the BCP 47 locale to translate to, will fall back to English as the base locale for Matrix SAS Emoji.
+ */
+export function tEmoji(emoji: SasEmoji, locale: string): string {
+ const mapping = SasEmojiMap.get(emoji);
+ if (!mapping) {
+ throw new Error(`Emoji mapping not found for emoji ${emoji}`);
+ }
+
+ const [description, translations] = mapping;
+
+ for (const key of getNormalizedLanguageKeys(locale)) {
+ if (translations[key]) {
+ return translations[key];
+ }
+ }
+
+ return description;
+}
diff --git a/packages/shared-components/src/crypto/SasEmoji/__snapshots__/SasEmoji.test.tsx.snap b/packages/shared-components/src/crypto/SasEmoji/__snapshots__/SasEmoji.test.tsx.snap
new file mode 100644
index 0000000000..27f53cb870
--- /dev/null
+++ b/packages/shared-components/src/crypto/SasEmoji/__snapshots__/SasEmoji.test.tsx.snap
@@ -0,0 +1,115 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[` > should match snapshot 1`] = `
+
+
+
+
+ 🦋
+
+
+ Butterfly
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/packages/shared-components/src/crypto/SasEmoji/index.ts b/packages/shared-components/src/crypto/SasEmoji/index.ts
new file mode 100644
index 0000000000..5c6dc18fb7
--- /dev/null
+++ b/packages/shared-components/src/crypto/SasEmoji/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright 2026 Element Creations Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
+ * Please see LICENSE files in the repository root for full details.
+ */
+
+export { SasEmoji } from "./SasEmoji.tsx";
diff --git a/packages/shared-components/src/i18n/strings/hu.json b/packages/shared-components/src/i18n/strings/hu.json
index 559dec09d8..3ea03a5aef 100644
--- a/packages/shared-components/src/i18n/strings/hu.json
+++ b/packages/shared-components/src/i18n/strings/hu.json
@@ -43,12 +43,15 @@
}
},
"room_list": {
+ "appearance": "Megjelenítés",
"open_space_menu": "Tér menü megnyitása",
"room_options": "Szobabeállítások",
+ "show_message_previews": "Üzenetelőnézetek megjelenítése",
"sort": "Rendezés",
"sort_type": {
"activity": "Tevékenység",
- "atoz": "A–Z"
+ "atoz": "A–Z",
+ "unread_first": "Olvasatlan elöl"
},
"space_menu": {
"home": "Kezdő tér",
diff --git a/packages/shared-components/src/index.ts b/packages/shared-components/src/index.ts
index ae7770e4d7..370f7d038b 100644
--- a/packages/shared-components/src/index.ts
+++ b/packages/shared-components/src/index.ts
@@ -12,6 +12,7 @@ export * from "./audio/PlayPauseButton";
export * from "./audio/SeekBar";
export * from "./avatar/AvatarWithDetails";
export * from "./composer/Banner";
+export * from "./crypto/SasEmoji";
export * from "./event-tiles/TextualEventView";
export * from "./message-body/MediaBody";
export * from "./pill-input/Pill";
diff --git a/packages/shared-components/yarn.lock b/packages/shared-components/yarn.lock
index 2341936db5..62e161d50d 100644
--- a/packages/shared-components/yarn.lock
+++ b/packages/shared-components/yarn.lock
@@ -667,6 +667,11 @@
dependencies:
"@babel/runtime" "^7.17.9"
+"@matrix-org/spec@^1.7.0":
+ version "1.16.0"
+ resolved "https://registry.yarnpkg.com/@matrix-org/spec/-/spec-1.16.0.tgz#c88f4ed521e4c0bd3a4c108bcaf13f25173a0fdc"
+ integrity sha512-xUKHkwGXXISMCfTrx6JW6uGEK5O8IeZVOjBm7FX1h/ihpK6l50nlSIMRYdtz4V6q3pvOVBOCft4hPYTJVeTZDA==
+
"@mdx-js/react@^3.0.0":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.1.1.tgz#24bda7fffceb2fe256f954482123cda1be5f5fef"
diff --git a/playwright/e2e/crypto/utils.ts b/playwright/e2e/crypto/utils.ts
index 8b677ed4cb..4aab27f51a 100644
--- a/playwright/e2e/crypto/utils.ts
+++ b/playwright/e2e/crypto/utils.ts
@@ -290,7 +290,7 @@ export async function doTwoWaySasVerification(page: Page, verifier: JSHandle div");
await expect(emojiBlocks).toHaveCount(emojis.length);
// then, check that our application shows an emoji panel with the same emojis.
diff --git a/playwright/testcontainers/mas.ts b/playwright/testcontainers/mas.ts
index 4fcaa93ce6..4f1a86aa83 100644
--- a/playwright/testcontainers/mas.ts
+++ b/playwright/testcontainers/mas.ts
@@ -10,7 +10,7 @@ import {
type StartedPostgreSqlContainer,
} from "@element-hq/element-web-playwright-common/lib/testcontainers";
-const TAG = "main@sha256:a3d8276ff5878ce109f184fd2f0e27fc50e1cb4ec409ad5217022074b19a7fd9";
+const TAG = "main@sha256:d0d03f9067c7977807131a9c739c9ed9f081063d1a1c21bee66204e40c44aa50";
/**
* MatrixAuthenticationServiceContainer which freezes the docker digest to
diff --git a/res/css/views/verification/_VerificationShowSas.pcss b/res/css/views/verification/_VerificationShowSas.pcss
index 9e4d1f138b..57fae76788 100644
--- a/res/css/views/verification/_VerificationShowSas.pcss
+++ b/res/css/views/verification/_VerificationShowSas.pcss
@@ -20,40 +20,9 @@ Please see LICENSE files in the repository root for full details.
}
.mx_VerificationShowSas_emojiSas {
- text-align: center;
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
margin: 25px 0;
}
-.mx_VerificationShowSas_emojiSas_block {
- display: inline-block;
- margin-bottom: 16px;
- position: relative;
- width: 52px;
-}
-
-.mx_Dialog .mx_VerificationShowSas_emojiSas_block,
-.mx_AuthPage_modal .mx_VerificationShowSas_emojiSas_block {
- width: 60px;
-}
-
-.mx_VerificationShowSas_emojiSas_emoji {
- font-size: $font-32px;
- /* Use the Twemoji font for consistency with other clients */
- font-family: Twemoji, var(--cpd-font-family-sans);
-}
-
-.mx_VerificationShowSas_emojiSas_label {
- font-size: $font-12px;
- word-break: break-word;
-}
-
-.mx_VerificationShowSas_emojiSas_break {
- flex-basis: 100%;
-}
-
.mx_VerificationShowSas_buttonRow {
text-align: center;
display: flex;
diff --git a/src/components/views/rooms/RoomHeader/RoomHeader.tsx b/src/components/views/rooms/RoomHeader/RoomHeader.tsx
index 2434d5900e..b6c0086800 100644
--- a/src/components/views/rooms/RoomHeader/RoomHeader.tsx
+++ b/src/components/views/rooms/RoomHeader/RoomHeader.tsx
@@ -22,7 +22,7 @@ import { HistoryVisibility, JoinRule, type Room } from "matrix-js-sdk/src/matrix
import { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
import { Flex, Box } from "@element-hq/web-shared-components";
import { CallType } from "matrix-js-sdk/src/webrtc/call";
-import { HistoryIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
+import { HistoryIcon, UserProfileSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { useRoomName } from "../../../../hooks/useRoomName.ts";
import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStorePhases.ts";
@@ -391,6 +391,40 @@ function RoomHeaderButtons({
);
}
+/** Create an icon to warn the user about shared history visibility, in encrypted rooms.
+ *
+ * Note that we use the same icon as in the room summary card and elsewhere, to aid user recognition.
+ */
+function historyVisibilityIcon(historyVisibility: HistoryVisibility): JSX.Element | null {
+ if (historyVisibility === HistoryVisibility.Shared) {
+ return (
+
+
+
+ );
+ } else if (historyVisibility === HistoryVisibility.WorldReadable) {
+ return (
+
+
+
+ );
+ } else {
+ return null;
+ }
+}
+
export default function RoomHeader({
room,
additionalButtons,
@@ -490,20 +524,7 @@ export default function RoomHeader({
)}
- {isRoomEncrypted &&
- historySharingEnabled &&
- (historyVisibility === HistoryVisibility.Shared ||
- historyVisibility === HistoryVisibility.WorldReadable) && (
-
-
-
- )}
+ {isRoomEncrypted && historySharingEnabled && historyVisibilityIcon(historyVisibility)}
diff --git a/src/components/views/verification/VerificationShowSas.tsx b/src/components/views/verification/VerificationShowSas.tsx
index 0f6272a885..84490e556b 100644
--- a/src/components/views/verification/VerificationShowSas.tsx
+++ b/src/components/views/verification/VerificationShowSas.tsx
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
-import React from "react";
+import React, { type ComponentProps } from "react";
import { type Device } from "matrix-js-sdk/src/matrix";
-import { type GeneratedSas, type EmojiMapping } from "matrix-js-sdk/src/crypto-api";
-import SasEmoji from "@matrix-org/spec/sas-emoji.json";
+import { type GeneratedSas } from "matrix-js-sdk/src/crypto-api";
+import { SasEmoji } from "@element-hq/web-shared-components";
-import { _t, getNormalizedLanguageKeys, getUserLanguage } from "../../../languageHandler";
+import { _t } from "../../../languageHandler";
import { PendingActionSpinner } from "../right_panel/EncryptionInfo";
import AccessibleButton from "../elements/AccessibleButton";
@@ -34,52 +34,6 @@ interface IState {
cancelling?: boolean;
}
-const SasEmojiMap = new Map<
- string, // lowercase
- {
- description: string;
- translations: {
- [normalizedLanguageKey: string]: string;
- };
- }
->(
- SasEmoji.map(({ description, translated_descriptions: translations }) => [
- description.toLowerCase(),
- {
- description,
- // Normalize the translation keys
- translations: Object.keys(translations).reduce>((o, k) => {
- for (const key of getNormalizedLanguageKeys(k)) {
- o[key] = translations[k as keyof typeof translations]!;
- }
- return o;
- }, {}),
- },
- ]),
-);
-
-/**
- * Translate given EmojiMapping into the target locale
- * @param mapping - the given EmojiMapping to translate
- * @param locale - the BCP 47 locale to translate to, will fall back to English as the base locale for Matrix SAS Emoji.
- */
-export function tEmoji(mapping: EmojiMapping, locale: string): string {
- const name = mapping[1];
- const emoji = SasEmojiMap.get(name.toLowerCase());
- if (!emoji) {
- console.warn("Emoji not found for translation", name);
- return name;
- }
-
- for (const key of getNormalizedLanguageKeys(locale)) {
- if (!!emoji.translations[key]) {
- return emoji.translations[key];
- }
- }
-
- return emoji.description;
-}
-
export default class VerificationShowSas extends React.Component {
public constructor(props: IProps) {
super(props);
@@ -100,25 +54,14 @@ export default class VerificationShowSas extends React.Component
};
public render(): React.ReactNode {
- const locale = getUserLanguage();
-
let sasDisplay;
let sasCaption;
if (this.props.sas.emoji) {
- const emojiBlocks = this.props.sas.emoji.map((emoji, i) => (
-
-
- {emoji[0]}
-
-
{tEmoji(emoji, locale)}
-
- ));
sasDisplay = (
-
- {emojiBlocks.slice(0, 4)}
-
- {emojiBlocks.slice(4)}
-
+ e[0]) as ComponentProps["emoji"]}
+ />
);
sasCaption = this.props.isSelf
? _t("encryption|verification|confirm_the_emojis")
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index e899fec982..7749614825 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2020,7 +2020,8 @@
"other": "%(count)s people asking to join"
},
"room_is_public": "This room is public",
- "shared_history_tooltip": "New members see history"
+ "shared_history_tooltip": "New members see history",
+ "world_readable_history_tooltip": "Anyone can see history"
},
"header_avatar_open_settings_label": "Open room settings",
"header_face_pile_tooltip": "People",
diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json
index bc0c8adfb8..295397a1df 100644
--- a/src/i18n/strings/hu.json
+++ b/src/i18n/strings/hu.json
@@ -2017,7 +2017,8 @@
"Csatlakozást kér": "one",
"%(count)s csatlakozást kérő ember": "other"
},
- "room_is_public": "Ez egy nyilvános szoba"
+ "room_is_public": "Ez egy nyilvános szoba",
+ "shared_history_tooltip": "Az új tagok látják az előzményeket"
},
"header_avatar_open_settings_label": "Szobabeállítások megnyitása",
"header_face_pile_tooltip": "Taglista váltása",
diff --git a/test/unit-tests/components/views/VerificationShowSas-test.tsx b/test/unit-tests/components/views/VerificationShowSas-test.tsx
deleted file mode 100644
index f9312e906e..0000000000
--- a/test/unit-tests/components/views/VerificationShowSas-test.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
-Copyright 2024 New Vector Ltd.
-Copyright 2023 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.
-*/
-
-import { type EmojiMapping } from "matrix-js-sdk/src/crypto-api";
-
-import { tEmoji } from "../../../../src/components/views/verification/VerificationShowSas";
-
-describe("tEmoji", () => {
- it.each([
- ["en-GB", "Dog"],
- ["en", "Dog"],
- ["de-DE", "Hund"],
- ["pt", "Cachorro"],
- ])("should handle locale %s", (locale, expectation) => {
- const emoji: EmojiMapping = ["🐶", "Dog"];
- expect(tEmoji(emoji, locale)).toEqual(expectation);
- });
-});
diff --git a/test/unit-tests/components/views/dialogs/IncomingSasDialog-test.tsx b/test/unit-tests/components/views/dialogs/IncomingSasDialog-test.tsx
index cf9e103ee6..2a7ed61f13 100644
--- a/test/unit-tests/components/views/dialogs/IncomingSasDialog-test.tsx
+++ b/test/unit-tests/components/views/dialogs/IncomingSasDialog-test.tsx
@@ -34,7 +34,7 @@ describe("IncomingSasDialog", () => {
it("should show some emojis once keys are exchanged", () => {
const mockVerifier = makeMockVerifier();
- const { container } = renderComponent(mockVerifier);
+ const { getAllByText } = renderComponent(mockVerifier);
// fire the ShowSas event
const sasEvent = makeMockSasCallbacks();
@@ -42,11 +42,8 @@ describe("IncomingSasDialog", () => {
mockVerifier.emit(VerifierEvent.ShowSas, sasEvent);
});
- const emojis = container.getElementsByClassName("mx_VerificationShowSas_emojiSas_block");
- expect(emojis.length).toEqual(7);
- for (const emoji of emojis) {
- expect(emoji).toHaveTextContent("🦄Unicorn");
- }
+ expect(getAllByText("🦄")).toHaveLength(7);
+ expect(getAllByText("Unicorn")).toHaveLength(7);
});
});
diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/VerificationRequestDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/VerificationRequestDialog-test.tsx.snap
index 79737988d0..79d21cf19f 100644
--- a/test/unit-tests/components/views/dialogs/__snapshots__/VerificationRequestDialog-test.tsx.snap
+++ b/test/unit-tests/components/views/dialogs/__snapshots__/VerificationRequestDialog-test.tsx.snap
@@ -456,41 +456,38 @@ exports[`VerificationRequestDialog When other device accepted emoji, displays em
Confirm that the emojis below match those shown on your other device.
", () => {
});
it("should show some emojis once keys are exchanged", () => {
- const { container } = renderComponent({
+ const { getAllByText } = renderComponent({
request: mockRequest,
phase: Phase.Started,
});
@@ -117,11 +117,8 @@ describe("", () => {
mockVerifier.emit(VerifierEvent.ShowSas, sasEvent);
});
- const emojis = container.getElementsByClassName("mx_VerificationShowSas_emojiSas_block");
- expect(emojis.length).toEqual(7);
- for (const emoji of emojis) {
- expect(emoji).toHaveTextContent("🦄Unicorn");
- }
+ expect(getAllByText("🦄")).toHaveLength(7);
+ expect(getAllByText("Unicorn")).toHaveLength(7);
});
describe("'Verify own device' flow", () => {
diff --git a/test/unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx b/test/unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx
index 7c1b5429cd..7de539ed54 100644
--- a/test/unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx
+++ b/test/unit-tests/components/views/rooms/RoomHeader/RoomHeader-test.tsx
@@ -744,6 +744,29 @@ describe("RoomHeader", () => {
expect(queryByLabelText(document.body, "New members see history")).not.toBeInTheDocument();
});
+ it("shows a user icon if the room is encrypted and has world readable history", async () => {
+ mocked(client.getCrypto()!).isEncryptionEnabledInRoom.mockResolvedValue(true);
+ await room.addLiveEvents(
+ [
+ new MatrixEvent({
+ type: "m.room.history_visibility",
+ content: { history_visibility: "world_readable" },
+ sender: MatrixClientPeg.get()!.getSafeUserId(),
+ state_key: "",
+ room_id: room.roomId,
+ }),
+ ],
+ { addToState: true },
+ );
+ const featureEnabled = true;
+ jest.spyOn(SettingsStore, "getValue").mockImplementation(
+ (flag) => flag === "feature_share_history_on_invite" && featureEnabled,
+ );
+
+ render(, getWrapper());
+ await waitFor(() => getByLabelText(document.body, "Anyone can see history"));
+ });
+
describe("dm", () => {
beforeEach(() => {
// Make the mocked room a DM
diff --git a/test/unit-tests/components/views/rooms/RoomHeader/__snapshots__/RoomHeader-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomHeader/__snapshots__/RoomHeader-test.tsx.snap
index 404edd3c6a..e3ff02aeaf 100644
--- a/test/unit-tests/components/views/rooms/RoomHeader/__snapshots__/RoomHeader-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/RoomHeader/__snapshots__/RoomHeader-test.tsx.snap
@@ -56,7 +56,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
style="--cpd-icon-button-size: 100%;"
>