Show an icon in the room header for shared history (#31879)
* Show an icon in the room header for shared history
Add a decoration to the header for encrypted rooms with `history_visibility:
{shared|public}`.
Fixes: #31858
* Gate "shared history icon" behind labs flag
... since history isn't actually shared unless the flag is on
* Update snapshots
* update screenshot
* update screenshots, again
* exclude RRs from screenshot test
This commit is contained in:
@@ -50,7 +50,9 @@ test.describe("History sharing", function () {
|
|||||||
// Bob should now be able to decrypt the event
|
// Bob should now be able to decrypt the event
|
||||||
await expect(bobPage.getByText("A message from Alice")).toBeVisible();
|
await expect(bobPage.getByText("A message from Alice")).toBeVisible();
|
||||||
|
|
||||||
const mask = [bobPage.locator(".mx_MessageTimestamp")];
|
// Exclude message timestamps and RR avatars from the screenshot. Bob sometimes sees Alice's RR on the
|
||||||
|
// previous event, which is surprising but not what we're testing here.
|
||||||
|
const mask = [bobPage.locator(".mx_MessageTimestamp"), bobPage.locator(".mx_ReadReceiptGroup_container")];
|
||||||
await expect(bobPage.locator(".mx_RoomView_body")).toMatchScreenshot("shared-history-invite-accepted.png", {
|
await expect(bobPage.locator(".mx_RoomView_body")).toMatchScreenshot("shared-history-invite-accepted.png", {
|
||||||
mask,
|
mask,
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
@@ -18,10 +18,11 @@ import NotificationsIcon from "@vector-im/compound-design-tokens/assets/web/icon
|
|||||||
import VerifiedIcon from "@vector-im/compound-design-tokens/assets/web/icons/verified";
|
import VerifiedIcon from "@vector-im/compound-design-tokens/assets/web/icons/verified";
|
||||||
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
|
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
|
||||||
import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
|
import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
|
||||||
import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
|
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 { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
|
||||||
import { Flex, Box } from "@element-hq/web-shared-components";
|
import { Flex, Box } from "@element-hq/web-shared-components";
|
||||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||||
|
import { HistoryIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
|
|
||||||
import { useRoomName } from "../../../../hooks/useRoomName.ts";
|
import { useRoomName } from "../../../../hooks/useRoomName.ts";
|
||||||
import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStorePhases.ts";
|
import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStorePhases.ts";
|
||||||
@@ -55,6 +56,7 @@ import { useScopedRoomContext } from "../../../../contexts/ScopedRoomContext.tsx
|
|||||||
import { ToggleableIcon } from "./toggle/ToggleableIcon.tsx";
|
import { ToggleableIcon } from "./toggle/ToggleableIcon.tsx";
|
||||||
import { CurrentRightPanelPhaseContextProvider } from "../../../../contexts/CurrentRightPanelPhaseContext.tsx";
|
import { CurrentRightPanelPhaseContextProvider } from "../../../../contexts/CurrentRightPanelPhaseContext.tsx";
|
||||||
import { LocalRoom } from "../../../../models/LocalRoom.ts";
|
import { LocalRoom } from "../../../../models/LocalRoom.ts";
|
||||||
|
import { useIsEncrypted } from "../../../../hooks/useIsEncrypted.ts";
|
||||||
|
|
||||||
function RoomHeaderButtons({
|
function RoomHeaderButtons({
|
||||||
room,
|
room,
|
||||||
@@ -401,8 +403,11 @@ export default function RoomHeader({
|
|||||||
const client = useMatrixClientContext();
|
const client = useMatrixClientContext();
|
||||||
const roomName = useRoomName(room);
|
const roomName = useRoomName(room);
|
||||||
const joinRule = useRoomState(room, (state) => state.getJoinRule());
|
const joinRule = useRoomState(room, (state) => state.getJoinRule());
|
||||||
|
const historyVisibility = useRoomState(room, (state) => state.getHistoryVisibility());
|
||||||
|
const historySharingEnabled = useFeatureEnabled("feature_share_history_on_invite");
|
||||||
const dmMember = useDmMember(room);
|
const dmMember = useDmMember(room);
|
||||||
const isDirectMessage = !!dmMember;
|
const isDirectMessage = !!dmMember;
|
||||||
|
const isRoomEncrypted = useIsEncrypted(client, room);
|
||||||
const e2eStatus = useEncryptionStatus(client, room);
|
const e2eStatus = useEncryptionStatus(client, room);
|
||||||
const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");
|
const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");
|
||||||
const onAvatarClick = (): void => {
|
const onAvatarClick = (): void => {
|
||||||
@@ -484,6 +489,21 @@ export default function RoomHeader({
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isRoomEncrypted &&
|
||||||
|
historySharingEnabled &&
|
||||||
|
(historyVisibility === HistoryVisibility.Shared ||
|
||||||
|
historyVisibility === HistoryVisibility.WorldReadable) && (
|
||||||
|
<Tooltip label={_t("room|header|shared_history_tooltip")} placement="right">
|
||||||
|
<HistoryIcon
|
||||||
|
width="16px"
|
||||||
|
height="16px"
|
||||||
|
className="mx_RoomHeader_icon"
|
||||||
|
color="var(--cpd-color-icon-info-primary)"
|
||||||
|
aria-label={_t("room|header|shared_history_tooltip")}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -2031,7 +2031,8 @@
|
|||||||
"one": "Asking to join",
|
"one": "Asking to join",
|
||||||
"other": "%(count)s people asking to join"
|
"other": "%(count)s people asking to join"
|
||||||
},
|
},
|
||||||
"room_is_public": "This room is public"
|
"room_is_public": "This room is public",
|
||||||
|
"shared_history_tooltip": "New members see history"
|
||||||
},
|
},
|
||||||
"header_avatar_open_settings_label": "Open room settings",
|
"header_avatar_open_settings_label": "Open room settings",
|
||||||
"header_face_pile_tooltip": "People",
|
"header_face_pile_tooltip": "People",
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import WidgetStore, { type IApp } from "../../../../../../src/stores/WidgetStore
|
|||||||
import { UIFeature } from "../../../../../../src/settings/UIFeature";
|
import { UIFeature } from "../../../../../../src/settings/UIFeature";
|
||||||
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
|
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
|
||||||
import { ElementCallMemberEventType } from "../../../../../../src/call-types";
|
import { ElementCallMemberEventType } from "../../../../../../src/call-types";
|
||||||
|
import { defaultWatchManager } from "../../../../../../src/settings/Settings.tsx";
|
||||||
|
|
||||||
jest.mock("../../../../../../src/utils/ShieldUtils");
|
jest.mock("../../../../../../src/utils/ShieldUtils");
|
||||||
jest.mock("../../../../../../src/hooks/right-panel/useCurrentPhase", () => ({
|
jest.mock("../../../../../../src/hooks/right-panel/useCurrentPhase", () => ({
|
||||||
@@ -100,7 +101,7 @@ describe("RoomHeader", () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(() => {
|
||||||
client = stubClient();
|
client = stubClient();
|
||||||
room = new Room(ROOM_ID, client, "@alice:example.org", {
|
room = new Room(ROOM_ID, client, "@alice:example.org", {
|
||||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||||
@@ -708,6 +709,41 @@ describe("RoomHeader", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("shows a history icon if the room is encrypted and has shared history", async () => {
|
||||||
|
mocked(client.getCrypto()!).isEncryptionEnabledInRoom.mockResolvedValue(true);
|
||||||
|
await room.addLiveEvents(
|
||||||
|
[
|
||||||
|
new MatrixEvent({
|
||||||
|
type: "m.room.history_visibility",
|
||||||
|
content: { history_visibility: "shared" },
|
||||||
|
sender: MatrixClientPeg.get()!.getSafeUserId(),
|
||||||
|
state_key: "",
|
||||||
|
room_id: room.roomId,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
{ addToState: true },
|
||||||
|
);
|
||||||
|
let featureEnabled = true;
|
||||||
|
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||||
|
(flag) => flag === "feature_share_history_on_invite" && featureEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
render(<RoomHeader room={room} />, getWrapper());
|
||||||
|
await waitFor(() => getByLabelText(document.body, "New members see history"));
|
||||||
|
|
||||||
|
// Disable the labs flag and check the icon disappears
|
||||||
|
featureEnabled = false;
|
||||||
|
act(() =>
|
||||||
|
defaultWatchManager.notifyUpdate(
|
||||||
|
"feature_share_history_on_invite",
|
||||||
|
null,
|
||||||
|
SettingLevel.DEVICE,
|
||||||
|
featureEnabled,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(queryByLabelText(document.body, "New members see history")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
describe("dm", () => {
|
describe("dm", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Make the mocked room a DM
|
// Make the mocked room a DM
|
||||||
|
|||||||
+4
-4
@@ -56,7 +56,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
|||||||
style="--cpd-icon-button-size: 100%;"
|
style="--cpd-icon-button-size: 100%;"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
aria-labelledby="_r_12c_"
|
aria-labelledby="_r_134_"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="1em"
|
height="1em"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -83,7 +83,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
|||||||
style="--cpd-icon-button-size: 100%;"
|
style="--cpd-icon-button-size: 100%;"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
aria-labelledby="_r_12h_"
|
aria-labelledby="_r_139_"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="1em"
|
height="1em"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -98,7 +98,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-label="Threads"
|
aria-label="Threads"
|
||||||
aria-labelledby="_r_12m_"
|
aria-labelledby="_r_13e_"
|
||||||
class="_icon-button_1215g_8"
|
class="_icon-button_1215g_8"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="button"
|
role="button"
|
||||||
@@ -125,7 +125,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-label="Room info"
|
aria-label="Room info"
|
||||||
aria-labelledby="_r_12r_"
|
aria-labelledby="_r_13j_"
|
||||||
class="_icon-button_1215g_8"
|
class="_icon-button_1215g_8"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
Reference in New Issue
Block a user