Remove "history may be shared" banner. (#31881)
* Revert "Update algorithm for history visible banner. (#31577)" This reverts commitce9c66ba4c. * Revert "Update prop type & documentation for HistoryVisibleBanner and VM. (#31545)" This reverts commit4da149e56f. * Revert "Prevent history visible banner from displaying in threads. (#31535)" This reverts commitc7134e8532. * Revert "Implement UI for history visibility acknowledgement. (#31156)" This reverts commitcff9119324.
This commit is contained in:
-42
@@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 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 { type Meta, type StoryFn } from "@storybook/react-vite";
|
|
||||||
import React, { type JSX } from "react";
|
|
||||||
import { fn } from "storybook/test";
|
|
||||||
|
|
||||||
import { useMockedViewModel } from "../../viewmodel";
|
|
||||||
import {
|
|
||||||
HistoryVisibleBannerView,
|
|
||||||
type HistoryVisibleBannerViewActions,
|
|
||||||
type HistoryVisibleBannerViewSnapshot,
|
|
||||||
} from "./HistoryVisibleBannerView";
|
|
||||||
|
|
||||||
type HistoryVisibleBannerProps = HistoryVisibleBannerViewSnapshot & HistoryVisibleBannerViewActions;
|
|
||||||
|
|
||||||
const HistoryVisibleBannerViewWrapper = ({ onClose, ...rest }: HistoryVisibleBannerProps): JSX.Element => {
|
|
||||||
const vm = useMockedViewModel(rest, {
|
|
||||||
onClose,
|
|
||||||
});
|
|
||||||
return <HistoryVisibleBannerView vm={vm} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "composer/HistoryVisibleBannerView",
|
|
||||||
component: HistoryVisibleBannerViewWrapper,
|
|
||||||
tags: ["autodocs"],
|
|
||||||
argTypes: {},
|
|
||||||
args: {
|
|
||||||
visible: true,
|
|
||||||
onClose: fn(),
|
|
||||||
},
|
|
||||||
} as Meta<typeof HistoryVisibleBannerViewWrapper>;
|
|
||||||
|
|
||||||
const Template: StoryFn<typeof HistoryVisibleBannerViewWrapper> = (args) => (
|
|
||||||
<HistoryVisibleBannerViewWrapper {...args} />
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 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 from "react";
|
|
||||||
import { render } from "@test-utils";
|
|
||||||
import { composeStories } from "@storybook/react-vite";
|
|
||||||
import { describe, it, vi, expect } from "vitest";
|
|
||||||
|
|
||||||
import * as stories from "./HistoryVisibleBannerView.stories.tsx";
|
|
||||||
|
|
||||||
const { Default } = composeStories(stories);
|
|
||||||
|
|
||||||
describe("HistoryVisibleBannerView", () => {
|
|
||||||
it("renders a history visible banner", () => {
|
|
||||||
const dismissFn = vi.fn();
|
|
||||||
|
|
||||||
const { container } = render(<Default onClose={dismissFn} />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
|
|
||||||
const button = container.querySelector("button");
|
|
||||||
expect(button).not.toBeNull();
|
|
||||||
button?.click();
|
|
||||||
expect(dismissFn).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
-78
@@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 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 { Link } from "@vector-im/compound-web";
|
|
||||||
import React, { type JSX } from "react";
|
|
||||||
|
|
||||||
import { _t } from "../../utils/i18n";
|
|
||||||
import { type ViewModel, useViewModel } from "../../viewmodel";
|
|
||||||
import { Banner } from "../Banner";
|
|
||||||
|
|
||||||
export interface HistoryVisibleBannerViewActions {
|
|
||||||
/**
|
|
||||||
* Called when the user dismisses the banner.
|
|
||||||
*/
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HistoryVisibleBannerViewSnapshot {
|
|
||||||
/**
|
|
||||||
* Whether the banner is currently visible.
|
|
||||||
*/
|
|
||||||
visible: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The view model for the banner.
|
|
||||||
*/
|
|
||||||
export type HistoryVisibleBannerViewModel = ViewModel<HistoryVisibleBannerViewSnapshot> &
|
|
||||||
HistoryVisibleBannerViewActions;
|
|
||||||
|
|
||||||
interface HistoryVisibleBannerViewProps {
|
|
||||||
/**
|
|
||||||
* The view model for the banner.
|
|
||||||
*/
|
|
||||||
vm: HistoryVisibleBannerViewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A component to alert that history is shared to new members of the room.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```tsx
|
|
||||||
* <HistoryVisibleBannerView vm={historyVisibleBannerViewModel} />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function HistoryVisibleBannerView({ vm }: Readonly<HistoryVisibleBannerViewProps>): JSX.Element {
|
|
||||||
const { visible } = useViewModel(vm);
|
|
||||||
|
|
||||||
const contents = _t(
|
|
||||||
"room|status_bar|history_visible",
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
a: substituteATag,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{visible && (
|
|
||||||
<Banner type="info" onClose={() => vm.onClose()}>
|
|
||||||
{contents}
|
|
||||||
</Banner>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function substituteATag(sub: string): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Link href="https://element.io/en/help#e2ee-history-sharing" target="_blank">
|
|
||||||
{sub}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
-62
@@ -1,62 +0,0 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
||||||
|
|
||||||
exports[`HistoryVisibleBannerView > renders a history visible banner 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="banner"
|
|
||||||
data-type="info"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
fill="currentColor"
|
|
||||||
font-size="24"
|
|
||||||
height="1em"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="1em"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="content"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
This room has been configured so that new members can read history.
|
|
||||||
<a
|
|
||||||
class="_link_1v5rz_8"
|
|
||||||
data-kind="primary"
|
|
||||||
data-size="medium"
|
|
||||||
href="https://element.io/en/help#e2ee-history-sharing"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Learn More
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="actions"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="_button_13vu4_8"
|
|
||||||
data-kind="secondary"
|
|
||||||
data-size="sm"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
Dismiss
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 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 * from "./HistoryVisibleBannerView";
|
|
||||||
@@ -32,7 +32,6 @@
|
|||||||
"exceeded_resource_limit_description": "Please contact your service administrator to continue using the service.",
|
"exceeded_resource_limit_description": "Please contact your service administrator to continue using the service.",
|
||||||
"exceeded_resource_limit_title": "Your message wasn't sent because this homeserver has exceeded a resource limit.",
|
"exceeded_resource_limit_title": "Your message wasn't sent because this homeserver has exceeded a resource limit.",
|
||||||
"failed_to_create_room_title": "Could not start a chat with this user",
|
"failed_to_create_room_title": "Could not start a chat with this user",
|
||||||
"history_visible": "This room has been configured so that new members can read history. <a>Learn More</a>",
|
|
||||||
"homeserver_blocked_title": "Your message wasn't sent because this homeserver has been blocked by its administrator.",
|
"homeserver_blocked_title": "Your message wasn't sent because this homeserver has been blocked by its administrator.",
|
||||||
"monthly_user_limit_reached_title": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit.",
|
"monthly_user_limit_reached_title": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit.",
|
||||||
"requires_consent_agreement_title": "You can't send any messages until you review and agree to our terms and conditions.",
|
"requires_consent_agreement_title": "You can't send any messages until you review and agree to our terms and conditions.",
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export * from "./audio/PlayPauseButton";
|
|||||||
export * from "./audio/SeekBar";
|
export * from "./audio/SeekBar";
|
||||||
export * from "./avatar/AvatarWithDetails";
|
export * from "./avatar/AvatarWithDetails";
|
||||||
export * from "./composer/Banner";
|
export * from "./composer/Banner";
|
||||||
export * from "./composer/HistoryVisibleBannerView";
|
|
||||||
export * from "./event-tiles/TextualEventView";
|
export * from "./event-tiles/TextualEventView";
|
||||||
export * from "./message-body/MediaBody";
|
export * from "./message-body/MediaBody";
|
||||||
export * from "./pill-input/Pill";
|
export * from "./pill-input/Pill";
|
||||||
|
|||||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB |
@@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 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 { HistoryVisibleBannerView, useCreateAutoDisposedViewModel } from "@element-hq/web-shared-components";
|
|
||||||
import React from "react";
|
|
||||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
|
||||||
|
|
||||||
import { HistoryVisibleBannerViewModel } from "../../../viewmodels/composer/HistoryVisibleBannerViewModel";
|
|
||||||
|
|
||||||
/** Wrapper around {@link HistoryVisibleBannerViewModel} for the creation of an auto-disposed view model. */
|
|
||||||
export const HistoryVisibleBanner: React.FC<{
|
|
||||||
/** The room instance associated with this banner view model. */
|
|
||||||
room: Room;
|
|
||||||
|
|
||||||
/** Whether the current user can send messages in the room. */
|
|
||||||
canSendMessages: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If not null, specifies the ID of the thread currently being viewed in the thread timeline side view,
|
|
||||||
* where the banner view is displayed as a child of the message composer.
|
|
||||||
*/
|
|
||||||
threadId: string | null;
|
|
||||||
}> = (props) => {
|
|
||||||
const vm = useCreateAutoDisposedViewModel(() => new HistoryVisibleBannerViewModel(props));
|
|
||||||
return <HistoryVisibleBannerView vm={vm} />;
|
|
||||||
};
|
|
||||||
@@ -54,7 +54,6 @@ import { type MatrixClientProps, withMatrixClientHOC } from "../../../contexts/M
|
|||||||
import { UIFeature } from "../../../settings/UIFeature";
|
import { UIFeature } from "../../../settings/UIFeature";
|
||||||
import { formatTimeLeft } from "../../../DateUtils";
|
import { formatTimeLeft } from "../../../DateUtils";
|
||||||
import RoomReplacedSvg from "../../../../res/img/room_replaced.svg";
|
import RoomReplacedSvg from "../../../../res/img/room_replaced.svg";
|
||||||
import { HistoryVisibleBanner } from "../composer/HistoryVisibleBanner";
|
|
||||||
|
|
||||||
// The prefix used when persisting editor drafts to localstorage.
|
// The prefix used when persisting editor drafts to localstorage.
|
||||||
export const WYSIWYG_EDITOR_STATE_STORAGE_PREFIX = "mx_wysiwyg_state_";
|
export const WYSIWYG_EDITOR_STATE_STORAGE_PREFIX = "mx_wysiwyg_state_";
|
||||||
@@ -675,11 +674,6 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes} ref={this.ref} role="region" aria-label={_t("a11y|message_composer")}>
|
<div className={classes} ref={this.ref} role="region" aria-label={_t("a11y|message_composer")}>
|
||||||
<HistoryVisibleBanner
|
|
||||||
room={this.props.room}
|
|
||||||
canSendMessages={canSendMessages}
|
|
||||||
threadId={threadId ?? null}
|
|
||||||
/>
|
|
||||||
<div className="mx_MessageComposer_wrapper">
|
<div className="mx_MessageComposer_wrapper">
|
||||||
<UserIdentityWarning room={this.props.room} key={this.props.room.roomId} />
|
<UserIdentityWarning room={this.props.room} key={this.props.room.roomId} />
|
||||||
<ReplyPreview
|
<ReplyPreview
|
||||||
|
|||||||
@@ -372,7 +372,6 @@ export interface Settings {
|
|||||||
"inviteRules": IBaseSetting<ComputedInviteConfig>;
|
"inviteRules": IBaseSetting<ComputedInviteConfig>;
|
||||||
"blockInvites": IBaseSetting<boolean>;
|
"blockInvites": IBaseSetting<boolean>;
|
||||||
"Developer.elementCallUrl": IBaseSetting<string>;
|
"Developer.elementCallUrl": IBaseSetting<string>;
|
||||||
"acknowledgedHistoryVisibility": IBaseSetting<boolean>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SettingKey = keyof Settings;
|
export type SettingKey = keyof Settings;
|
||||||
@@ -1499,8 +1498,4 @@ export const SETTINGS: Settings = {
|
|||||||
displayName: _td("devtools|settings|elementCallUrl"),
|
displayName: _td("devtools|settings|elementCallUrl"),
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
"acknowledgedHistoryVisibility": {
|
|
||||||
supportedLevels: [SettingLevel.ROOM_ACCOUNT],
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,184 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 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 {
|
|
||||||
BaseViewModel,
|
|
||||||
type HistoryVisibleBannerViewModel as HistoryVisibleBannerViewModelInterface,
|
|
||||||
type HistoryVisibleBannerViewSnapshot,
|
|
||||||
} from "@element-hq/web-shared-components";
|
|
||||||
import { HistoryVisibility, RoomStateEvent, type Room } from "matrix-js-sdk/src/matrix";
|
|
||||||
|
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
|
||||||
import { SettingLevel } from "../../settings/SettingLevel";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of {@link HistoryVisibility} levels that trigger the display of the history visible banner.
|
|
||||||
*/
|
|
||||||
const BANNER_VISIBLE_LEVELS = [HistoryVisibility.Shared, HistoryVisibility.WorldReadable];
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
/**
|
|
||||||
* The room instance associated with this banner view model.
|
|
||||||
*/
|
|
||||||
room: Room;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the current user is able to send messages in this room.
|
|
||||||
*/
|
|
||||||
canSendMessages: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If not null, indicates the ID of the thread currently being viewed in the thread
|
|
||||||
* timeline side view, where the banner view is displayed as a child of the message
|
|
||||||
* composer.
|
|
||||||
*/
|
|
||||||
threadId: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View model for the history visible banner, which prompts users that the current room
|
|
||||||
* history may be shared with new invitees, if they have not already acknowledged the
|
|
||||||
* banner.
|
|
||||||
*
|
|
||||||
* The view model operates using a simple 2-case algorithm:
|
|
||||||
*
|
|
||||||
* 1. When a user opens an encrypted room where `history_visibility` is not set to `joined`,
|
|
||||||
* and the user hasn't previously dismissed it for this particular room, display a banner.
|
|
||||||
* If the user dismisses the banner, update the client's local store to record that the
|
|
||||||
* banner has been dismissed.
|
|
||||||
* 2. When the user opens an encrypted room where `history_visibility` is set to `joined`, clear
|
|
||||||
* the dismissal flag if it was previously set. This ensures that if the room's history
|
|
||||||
* visibility changes from public to private and back to public, the banner will reappear
|
|
||||||
* when appropriate.
|
|
||||||
*
|
|
||||||
* This banner is only shown in the regular timeline view, not the thread timeline view, which is
|
|
||||||
* done by conditioning on the presence of `threadId` in the viewmodel's {@link Props}.
|
|
||||||
*
|
|
||||||
* See https://github.com/element-hq/element-meta/issues/2875 for more information.
|
|
||||||
*/
|
|
||||||
export class HistoryVisibleBannerViewModel
|
|
||||||
extends BaseViewModel<HistoryVisibleBannerViewSnapshot, Props>
|
|
||||||
implements HistoryVisibleBannerViewModelInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Watcher ID for the "feature_share_history_on_invite" setting.
|
|
||||||
*/
|
|
||||||
private readonly featureWatcher: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watcher ID for the "acknowledgedHistoryVisibility" setting specific to the room.
|
|
||||||
*/
|
|
||||||
private readonly acknowledgedWatcher: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes the latest banner snapshot given the VM's props.
|
|
||||||
* @param props - See {@link Props}.
|
|
||||||
* @returns The latest snapshot. See {@link HistoryVisibleBannerViewSnapshot}.
|
|
||||||
*/
|
|
||||||
private static readonly computeSnapshot = ({
|
|
||||||
room,
|
|
||||||
canSendMessages,
|
|
||||||
threadId,
|
|
||||||
}: Props): HistoryVisibleBannerViewSnapshot => {
|
|
||||||
const featureEnabled = SettingsStore.getValue("feature_share_history_on_invite");
|
|
||||||
const acknowledged = SettingsStore.getValue("acknowledgedHistoryVisibility", room.roomId);
|
|
||||||
const isHistoryVisible = BANNER_VISIBLE_LEVELS.includes(room.getHistoryVisibility());
|
|
||||||
|
|
||||||
// This implements point 1. of the algorithm described above. In the order below, all
|
|
||||||
// of the following must be true for the banner to display:
|
|
||||||
// - The room history sharing feature must be enabled.
|
|
||||||
// - The room must be encrypted.
|
|
||||||
// - The user must be able to send messages.
|
|
||||||
// - The history must be visible.
|
|
||||||
// - The view should not be part of a thread timeline.
|
|
||||||
// - The user must not have acknowledged the banner.
|
|
||||||
return {
|
|
||||||
visible:
|
|
||||||
featureEnabled &&
|
|
||||||
room.hasEncryptionStateEvent() &&
|
|
||||||
canSendMessages &&
|
|
||||||
isHistoryVisible &&
|
|
||||||
!threadId &&
|
|
||||||
!acknowledged,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new view model instance.
|
|
||||||
* @param props - Properties for this view model. See {@link Props}.
|
|
||||||
*/
|
|
||||||
public constructor(props: Props) {
|
|
||||||
super(props, HistoryVisibleBannerViewModel.computeSnapshot(props));
|
|
||||||
|
|
||||||
this.disposables.trackListener(props.room, RoomStateEvent.Update, () => this.setSnapshot());
|
|
||||||
|
|
||||||
// `SettingsStore` is not an `EventListener`, so we must manage these manually.
|
|
||||||
this.featureWatcher = SettingsStore.watchSetting(
|
|
||||||
"feature_share_history_on_invite",
|
|
||||||
null,
|
|
||||||
(_key, _roomId, _level, value: boolean) => this.setSnapshot(),
|
|
||||||
);
|
|
||||||
this.acknowledgedWatcher = SettingsStore.watchSetting(
|
|
||||||
"acknowledgedHistoryVisibility",
|
|
||||||
props.room.roomId,
|
|
||||||
(_key, _roomId, _level, value: boolean) => this.setSnapshot(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recompute and update this VM instance's snapshot. This will update the `acknowledgedHistoryVisibility`
|
|
||||||
* store entry if necessary.
|
|
||||||
*/
|
|
||||||
private setSnapshot(): void {
|
|
||||||
const acknowledged = SettingsStore.getValue("acknowledgedHistoryVisibility", this.props.room.roomId);
|
|
||||||
|
|
||||||
// Reset the acknowleded flag when the history visibility is set back to joined.
|
|
||||||
if (this.props.room.getHistoryVisibility() === HistoryVisibility.Joined && acknowledged) {
|
|
||||||
SettingsStore.setValue(
|
|
||||||
"acknowledgedHistoryVisibility",
|
|
||||||
this.props.room.roomId,
|
|
||||||
SettingLevel.ROOM_ACCOUNT,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.snapshot.set(HistoryVisibleBannerViewModel.computeSnapshot(this.props));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revoke the banner's acknoledgement status.
|
|
||||||
*/
|
|
||||||
public async revoke(): Promise<void> {
|
|
||||||
await SettingsStore.setValue(
|
|
||||||
"acknowledgedHistoryVisibility",
|
|
||||||
this.props.room.roomId,
|
|
||||||
SettingLevel.ROOM_ACCOUNT,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the user dismisses the banner.
|
|
||||||
*/
|
|
||||||
public async onClose(): Promise<void> {
|
|
||||||
await SettingsStore.setValue(
|
|
||||||
"acknowledgedHistoryVisibility",
|
|
||||||
this.props.room.roomId,
|
|
||||||
SettingLevel.ROOM_ACCOUNT,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispose of the viewmodel and its settings listeners.
|
|
||||||
*/
|
|
||||||
public dispose(): void {
|
|
||||||
super.dispose();
|
|
||||||
SettingsStore.unwatchSetting(this.featureWatcher);
|
|
||||||
SettingsStore.unwatchSetting(this.acknowledgedWatcher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import { mocked, type MockedObject } from "jest-mock";
|
import { mocked, type MockedObject } from "jest-mock";
|
||||||
import {
|
import {
|
||||||
type EventTimeline,
|
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
type Room,
|
type Room,
|
||||||
type User,
|
type User,
|
||||||
@@ -17,7 +16,7 @@ import {
|
|||||||
type IEvent,
|
type IEvent,
|
||||||
type RoomMember,
|
type RoomMember,
|
||||||
type MatrixClient,
|
type MatrixClient,
|
||||||
RoomState,
|
type EventTimeline,
|
||||||
EventType,
|
EventType,
|
||||||
type IEventRelation,
|
type IEventRelation,
|
||||||
type IUnsigned,
|
type IUnsigned,
|
||||||
@@ -30,9 +29,9 @@ import {
|
|||||||
JoinRule,
|
JoinRule,
|
||||||
type OidcClientConfig,
|
type OidcClientConfig,
|
||||||
type GroupCall,
|
type GroupCall,
|
||||||
HistoryVisibility,
|
|
||||||
type ICreateRoomOpts,
|
|
||||||
type EventStatus,
|
type EventStatus,
|
||||||
|
type ICreateRoomOpts,
|
||||||
|
RoomState,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||||
import { normalize } from "matrix-js-sdk/src/utils";
|
import { normalize } from "matrix-js-sdk/src/utils";
|
||||||
@@ -668,7 +667,6 @@ export function mkStubRoom(
|
|||||||
createThreadsTimelineSets: jest.fn().mockReturnValue(new Promise(() => {})),
|
createThreadsTimelineSets: jest.fn().mockReturnValue(new Promise(() => {})),
|
||||||
currentState: {
|
currentState: {
|
||||||
getStateEvents: jest.fn((_type, key) => (key === undefined ? [] : null)),
|
getStateEvents: jest.fn((_type, key) => (key === undefined ? [] : null)),
|
||||||
getHistoryVisibility: jest.fn().mockReturnValue(HistoryVisibility.Joined),
|
|
||||||
getMember: jest.fn(),
|
getMember: jest.fn(),
|
||||||
mayClientSendStateEvent: jest.fn().mockReturnValue(true),
|
mayClientSendStateEvent: jest.fn().mockReturnValue(true),
|
||||||
maySendStateEvent: jest.fn().mockReturnValue(true),
|
maySendStateEvent: jest.fn().mockReturnValue(true),
|
||||||
@@ -689,7 +687,6 @@ export function mkStubRoom(
|
|||||||
getCanonicalAlias: jest.fn(),
|
getCanonicalAlias: jest.fn(),
|
||||||
getDMInviter: jest.fn(),
|
getDMInviter: jest.fn(),
|
||||||
getEventReadUpTo: jest.fn(() => null),
|
getEventReadUpTo: jest.fn(() => null),
|
||||||
getHistoryVisibility: jest.fn().mockReturnValue(HistoryVisibility.Joined),
|
|
||||||
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(1),
|
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(1),
|
||||||
getJoinRule: jest.fn().mockReturnValue("invite"),
|
getJoinRule: jest.fn().mockReturnValue("invite"),
|
||||||
getJoinedMemberCount: jest.fn().mockReturnValue(1),
|
getJoinedMemberCount: jest.fn().mockReturnValue(1),
|
||||||
|
|||||||
-199
@@ -1,199 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Room } from "matrix-js-sdk/src/matrix";
|
|
||||||
|
|
||||||
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
|
|
||||||
import SettingsStore, { type CallbackFn } from "../../../../../src/settings/SettingsStore";
|
|
||||||
import { mkEvent, stubClient, upsertRoomStateEvents } from "../../../../test-utils";
|
|
||||||
import { HistoryVisibleBannerViewModel } from "../../../../../src/viewmodels/composer/HistoryVisibleBannerViewModel";
|
|
||||||
|
|
||||||
describe("HistoryVisibleBannerViewModel", () => {
|
|
||||||
const ROOM_ID = "!roomId:example.org";
|
|
||||||
|
|
||||||
let room: Room;
|
|
||||||
let watcherCallbacks: CallbackFn[];
|
|
||||||
let acknowledgedHistoryVisibility: boolean;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
watcherCallbacks = [];
|
|
||||||
acknowledgedHistoryVisibility = false;
|
|
||||||
|
|
||||||
jest.spyOn(SettingsStore, "setValue").mockImplementation(async (settingName, roomId, level, value) => {
|
|
||||||
if (settingName === "acknowledgedHistoryVisibility") {
|
|
||||||
acknowledgedHistoryVisibility = value;
|
|
||||||
}
|
|
||||||
watcherCallbacks.forEach((callbackFn) => callbackFn(settingName, roomId, level, value, value));
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId) => {
|
|
||||||
if (settingName === "acknowledgedHistoryVisibility") {
|
|
||||||
return acknowledgedHistoryVisibility;
|
|
||||||
}
|
|
||||||
if (settingName === "feature_share_history_on_invite") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return SettingsStore.getDefaultValue(settingName);
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(SettingsStore, "watchSetting").mockImplementation((settingName, roomId, callbackFn) => {
|
|
||||||
watcherCallbacks.push(callbackFn);
|
|
||||||
return `mockWatcherId-${settingName}-${roomId}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
stubClient();
|
|
||||||
room = new Room(ROOM_ID, {} as any, "@user:example.org");
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not show the banner in unencrypted rooms", () => {
|
|
||||||
const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: null });
|
|
||||||
expect(vm.getSnapshot().visible).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not show the banner in encrypted rooms with joined history visibility", () => {
|
|
||||||
upsertRoomStateEvents(room, [
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.encryption",
|
|
||||||
user: "@user1:server",
|
|
||||||
content: {},
|
|
||||||
}),
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.history_visibility",
|
|
||||||
content: {
|
|
||||||
history_visibility: "joined",
|
|
||||||
},
|
|
||||||
user: "@user1:server",
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: null });
|
|
||||||
expect(vm.getSnapshot().visible).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not show the banner if it has been dismissed", async () => {
|
|
||||||
await SettingsStore.setValue("acknowledgedHistoryVisibility", ROOM_ID, SettingLevel.ROOM_ACCOUNT, true);
|
|
||||||
upsertRoomStateEvents(room, [
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.encryption",
|
|
||||||
user: "@user1:server",
|
|
||||||
content: {},
|
|
||||||
}),
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.history_visibility",
|
|
||||||
user: "@user1:server",
|
|
||||||
content: {
|
|
||||||
history_visibility: "shared",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: null });
|
|
||||||
expect(vm.getSnapshot().visible).toBe(false);
|
|
||||||
vm.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not show the banner in threads", () => {
|
|
||||||
upsertRoomStateEvents(room, [
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.encryption",
|
|
||||||
user: "@user1:server",
|
|
||||||
content: {},
|
|
||||||
}),
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.history_visibility",
|
|
||||||
user: "@user1:server",
|
|
||||||
content: {
|
|
||||||
history_visibility: "shared",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: "some thread ID" });
|
|
||||||
expect(vm.getSnapshot().visible).toBe(false);
|
|
||||||
vm.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not show the banner if the user cannot send messages", () => {
|
|
||||||
upsertRoomStateEvents(room, [
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.encryption",
|
|
||||||
user: "@user1:server",
|
|
||||||
content: {},
|
|
||||||
}),
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.history_visibility",
|
|
||||||
user: "@user1:server",
|
|
||||||
content: {
|
|
||||||
history_visibility: "shared",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: false, threadId: null });
|
|
||||||
expect(vm.getSnapshot().visible).toBe(false);
|
|
||||||
vm.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not show the banner if history visibility is `invited`", () => {
|
|
||||||
upsertRoomStateEvents(room, [
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.encryption",
|
|
||||||
user: "@user1:server",
|
|
||||||
content: {},
|
|
||||||
}),
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.history_visibility",
|
|
||||||
user: "@user1:server",
|
|
||||||
content: {
|
|
||||||
history_visibility: "invited",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: null });
|
|
||||||
expect(vm.getSnapshot().visible).toBe(false);
|
|
||||||
vm.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should show the banner in encrypted rooms with shared history visibility", async () => {
|
|
||||||
upsertRoomStateEvents(room, [
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.encryption",
|
|
||||||
user: "@user1:server",
|
|
||||||
content: {},
|
|
||||||
}),
|
|
||||||
mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "m.room.history_visibility",
|
|
||||||
user: "@user1:server",
|
|
||||||
content: {
|
|
||||||
history_visibility: "shared",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: null });
|
|
||||||
expect(vm.getSnapshot().visible).toBe(true);
|
|
||||||
await vm.onClose();
|
|
||||||
expect(vm.getSnapshot().visible).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user