Merge branch 'develop' into t3chguy/wat/230.1

This commit is contained in:
Michael Telatynski
2024-09-18 11:21:55 +01:00
committed by GitHub
31 changed files with 523 additions and 155 deletions

View File

@@ -561,7 +561,6 @@ const onPinnedMessagesClick = (): void => {
};
function textForPinnedEvent(event: MatrixEvent, client: MatrixClient, allowJSX: boolean): (() => Renderable) | null {
if (!SettingsStore.getValue("feature_pinning")) return null;
const senderName = getSenderName(event);
const roomId = event.getRoomId()!;

View File

@@ -20,6 +20,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import getEntryComponentForLoginType, {
ContinueKind,
CustomAuthType,
IStageComponent,
} from "../views/auth/InteractiveAuthEntryComponents";
import Spinner from "../views/elements/Spinner";
@@ -75,11 +76,11 @@ export interface InteractiveAuthProps<T> {
// Called when the stage changes, or the stage's phase changes. First
// argument is the stage, second is the phase. Some stages do not have
// phases and will be counted as 0 (numeric).
onStagePhaseChange?(stage: AuthType | null, phase: number): void;
onStagePhaseChange?(stage: AuthType | CustomAuthType | null, phase: number): void;
}
interface IState {
authStage?: AuthType;
authStage?: CustomAuthType | AuthType;
stageState?: IStageStatus;
busy: boolean;
errorText?: string;

View File

@@ -140,7 +140,7 @@ import { cleanUpDraftsIfRequired } from "../../DraftCleaner";
// legacy export
export { default as Views } from "../../Views";
const AUTH_SCREENS = ["register", "login", "forgot_password", "start_sso", "start_cas", "welcome"];
const AUTH_SCREENS = ["register", "mobile_register", "login", "forgot_password", "start_sso", "start_cas", "welcome"];
// Actions that are redirected through the onboarding process prior to being
// re-dispatched. NOTE: some actions are non-trivial and would require
@@ -189,6 +189,7 @@ interface IState {
register_session_id?: string;
// eslint-disable-next-line camelcase
register_id_sid?: string;
isMobileRegistration?: boolean;
// When showing Modal dialogs we need to set aria-hidden on the root app element
// and disable it when there are no dialogs
hideToSRUsers: boolean;
@@ -243,6 +244,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
currentUserId: null,
hideToSRUsers: false,
isMobileRegistration: false,
syncError: null, // If the current syncing status is ERROR, the error object, otherwise null.
resizeNotifier: new ResizeNotifier(),
@@ -650,6 +652,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
case "require_registration":
startAnyRegistrationFlow(payload as any);
break;
case "start_mobile_registration":
this.startRegistration(payload.params || {}, true);
break;
case "start_registration":
if (Lifecycle.isSoftLogout()) {
this.onSoftLogout();
@@ -946,19 +951,28 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
});
}
private async startRegistration(params: { [key: string]: string }): Promise<void> {
private async startRegistration(params: { [key: string]: string }, isMobileRegistration?: boolean): Promise<void> {
if (!SettingsStore.getValue(UIFeature.Registration)) {
this.showScreen("welcome");
return;
}
const isMobileRegistrationAllowed =
isMobileRegistration && SettingsStore.getValue("Registration.mobileRegistrationHelper");
const newState: Partial<IState> = {
view: Views.REGISTER,
};
// Only honour params if they are all present, otherwise we reset
// HS and IS URLs when switching to registration.
if (params.client_secret && params.session_id && params.hs_url && params.is_url && params.sid) {
if (isMobileRegistrationAllowed && params.hs_url) {
try {
const config = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(params.hs_url);
newState.serverConfig = config;
} catch (err) {
logger.warn("Failed to load hs_url param:", params.hs_url);
}
} else if (params.client_secret && params.session_id && params.hs_url && params.is_url && params.sid) {
// Only honour params if they are all present, otherwise we reset
// HS and IS URLs when switching to registration.
newState.serverConfig = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
params.hs_url,
params.is_url,
@@ -978,10 +992,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
newState.register_id_sid = params.sid;
}
newState.isMobileRegistration = isMobileRegistrationAllowed;
this.setStateForNewView(newState);
ThemeController.isLogin = true;
this.themeWatcher.recheck();
this.notifyNewScreen("register");
this.notifyNewScreen(isMobileRegistrationAllowed ? "mobile_register" : "register");
}
// switch view to the given room
@@ -1721,6 +1737,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
params: params,
});
PerformanceMonitor.instance.start(PerformanceEntryNames.REGISTER);
} else if (screen === "mobile_register") {
dis.dispatch({
action: "start_mobile_registration",
params: params,
});
} else if (screen === "login") {
dis.dispatch({
action: "start_login",
@@ -2080,6 +2101,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
onServerConfigChange={this.onServerConfigChange}
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
fragmentAfterLogin={fragmentAfterLogin}
mobileRegister={this.state.isMobileRegistration}
{...this.getServerProperties()}
/>
);

View File

@@ -17,7 +17,6 @@ import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
import WidgetCard from "../views/right_panel/WidgetCard";
import SettingsStore from "../../settings/SettingsStore";
import MemberList from "../views/rooms/MemberList";
import UserInfo from "../views/right_panel/UserInfo";
import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo";
@@ -220,7 +219,7 @@ export default class RightPanel extends React.Component<Props, IState> {
break;
case RightPanelPhases.PinnedMessages:
if (!!this.props.room && SettingsStore.getValue("feature_pinning")) {
if (!!this.props.room) {
card = (
<PinnedMessagesCard
room={this.props.room}

View File

@@ -2408,13 +2408,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
</AuxPanel>
);
const isPinningEnabled = SettingsStore.getValue<boolean>("feature_pinning");
let pinnedMessageBanner;
if (isPinningEnabled) {
pinnedMessageBanner = (
<PinnedMessageBanner room={this.state.room} permalinkCreator={this.permalinkCreator} />
);
}
const pinnedMessageBanner = (
<PinnedMessageBanner room={this.state.room} permalinkCreator={this.permalinkCreator} />
);
let messageComposer;
const showComposer =

View File

@@ -53,6 +53,13 @@ const debuglog = (...args: any[]): void => {
}
};
export interface MobileRegistrationResponse {
user_id: string;
home_server: string;
access_token: string;
device_id: string;
}
interface IProps {
serverConfig: ValidatedServerConfig;
defaultDeviceDisplayName?: string;
@@ -62,7 +69,7 @@ interface IProps {
sessionId?: string;
idSid?: string;
fragmentAfterLogin?: string;
mobileRegister?: boolean;
// Called when the user has logged in. Params:
// - object with userId, deviceId, homeserverUrl, identityServerUrl, accessToken
// - The user's password, if available and applicable (may be cached in memory
@@ -410,18 +417,33 @@ export default class Registration extends React.Component<IProps, IState> {
debuglog("Registration: ui auth finished:", { hasEmail, hasAccessToken });
// dont log in if we found a session for a different user
if (hasAccessToken && !newState.differentLoggedInUserId) {
await this.props.onLoggedIn(
{
userId,
deviceId: (response as RegisterResponse).device_id!,
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
accessToken,
},
this.state.formVals.password!,
);
if (this.props.mobileRegister) {
const mobileResponse: MobileRegistrationResponse = {
user_id: userId,
home_server: this.state.matrixClient.getHomeserverUrl(),
access_token: accessToken,
device_id: (response as RegisterResponse).device_id!,
};
const event = new CustomEvent<MobileRegistrationResponse>("mobileregistrationresponse", {
detail: mobileResponse,
});
window.dispatchEvent(event);
newState.busy = false;
newState.completedNoSignin = true;
} else {
await this.props.onLoggedIn(
{
userId,
deviceId: (response as RegisterResponse).device_id!,
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
accessToken,
},
this.state.formVals.password!,
);
this.setupPushers();
this.setupPushers();
}
} else {
newState.busy = false;
newState.completedNoSignin = true;
@@ -558,7 +580,7 @@ export default class Registration extends React.Component<IProps, IState> {
);
} else if (this.state.matrixClient && this.state.flows.length) {
let ssoSection: JSX.Element | undefined;
if (this.state.ssoFlow) {
if (!this.props.mobileRegister && this.state.ssoFlow) {
let continueWithSection;
const providers = this.state.ssoFlow.identity_providers || [];
// when there is only a single (or 0) providers we show a wide button with `Continue with X` text
@@ -591,7 +613,6 @@ export default class Registration extends React.Component<IProps, IState> {
</React.Fragment>
);
}
return (
<React.Fragment>
{ssoSection}
@@ -660,7 +681,9 @@ export default class Registration extends React.Component<IProps, IState> {
let body;
if (this.state.completedNoSignin) {
let regDoneText;
if (this.state.differentLoggedInUserId) {
if (this.props.mobileRegister) {
regDoneText = undefined;
} else if (this.state.differentLoggedInUserId) {
regDoneText = (
<div>
<p>
@@ -717,6 +740,15 @@ export default class Registration extends React.Component<IProps, IState> {
{regDoneText}
</div>
);
} else if (this.props.mobileRegister) {
body = (
<Fragment>
<h1>{_t("auth|mobile_create_account_title", { hsName: this.props.serverConfig.hsName })}</h1>
{errorText}
{serverDeadSection}
{this.renderRegisterComponent()}
</Fragment>
);
} else {
body = (
<Fragment>
@@ -746,7 +778,9 @@ export default class Registration extends React.Component<IProps, IState> {
</Fragment>
);
}
if (this.props.mobileRegister) {
return <div className="mx_MobileRegister_body">{body}</div>;
}
return (
<AuthPage>
<AuthHeader />

View File

@@ -18,7 +18,6 @@ import DateSeparator from "../../views/messages/DateSeparator";
import HistoryTile from "../../views/rooms/HistoryTile";
import EventListSummary from "../../views/elements/EventListSummary";
import { SeparatorKind } from "../../views/messages/TimelineSeparator";
import SettingsStore from "../../../settings/SettingsStore";
const groupedStateEvents = [
EventType.RoomMember,
@@ -91,7 +90,7 @@ export class MainGrouper extends BaseGrouper {
return;
}
if (ev.getType() === EventType.RoomPinnedEvents && !SettingsStore.getValue("feature_pinning")) {
if (ev.getType() === EventType.RoomPinnedEvents) {
// If pinned messages are disabled, don't show the summary
return;
}

View File

@@ -11,6 +11,8 @@ import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { AuthType, AuthDict, IInputs, IStageStatus } from "matrix-js-sdk/src/interactive-auth";
import { logger } from "matrix-js-sdk/src/logger";
import React, { ChangeEvent, createRef, FormEvent, Fragment } from "react";
import { Button, Text } from "@vector-im/compound-web";
import PopOutIcon from "@vector-im/compound-design-tokens/assets/web/icons/pop-out";
import EmailPromptIcon from "../../../../res/img/element-icons/email-prompt.svg";
import { _t } from "../../../languageHandler";
@@ -21,6 +23,7 @@ import AccessibleButton, { AccessibleButtonKind, ButtonEvent } from "../elements
import Field from "../elements/Field";
import Spinner from "../elements/Spinner";
import CaptchaForm from "./CaptchaForm";
import { Flex } from "../../utils/Flex";
/* This file contains a collection of components which are used by the
* InteractiveAuth to prompt the user to enter the information needed
@@ -905,11 +908,11 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
}
}
export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
private popupWindow: Window | null;
private fallbackButton = createRef<HTMLButtonElement>();
export class FallbackAuthEntry<T = {}> extends React.Component<IAuthEntryProps & T> {
protected popupWindow: Window | null;
protected fallbackButton = createRef<HTMLButtonElement>();
public constructor(props: IAuthEntryProps) {
public constructor(props: IAuthEntryProps & T) {
super(props);
// we have to make the user click a button, as browsers will block
@@ -967,6 +970,50 @@ export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
}
}
export enum CustomAuthType {
// Workaround for MAS requiring non-UIA authentication for resetting cross-signing.
MasCrossSigningReset = "org.matrix.cross_signing_reset",
}
export class MasUnlockCrossSigningAuthEntry extends FallbackAuthEntry<{
stageParams?: {
url?: string;
};
}> {
public static LOGIN_TYPE = CustomAuthType.MasCrossSigningReset;
private onGoToAccountClick = (): void => {
if (!this.props.stageParams?.url) return;
this.popupWindow = window.open(this.props.stageParams.url, "_blank");
};
private onRetryClick = (): void => {
this.props.submitAuthDict({});
};
public render(): React.ReactNode {
return (
<div>
<Text>{_t("auth|uia|mas_cross_signing_reset_description")}</Text>
<Flex gap="var(--cpd-space-4x)">
<Button
Icon={PopOutIcon}
onClick={this.onGoToAccountClick}
autoFocus
kind="primary"
className="mx_Dialog_nonDialogButton"
>
{_t("auth|uia|mas_cross_signing_reset_cta")}
</Button>
<Button onClick={this.onRetryClick} kind="secondary" className="mx_Dialog_nonDialogButton">
{_t("action|retry")}
</Button>
</Flex>
</div>
);
}
}
export interface IStageComponentProps extends IAuthEntryProps {
stageParams?: Record<string, any>;
inputs?: IInputs;
@@ -983,8 +1030,10 @@ export interface IStageComponent extends React.ComponentClass<React.PropsWithRef
focus?(): void;
}
export default function getEntryComponentForLoginType(loginType: AuthType): IStageComponent {
export default function getEntryComponentForLoginType(loginType: AuthType | CustomAuthType): IStageComponent {
switch (loginType) {
case CustomAuthType.MasCrossSigningReset:
return MasUnlockCrossSigningAuthEntry;
case AuthType.Password:
return PasswordAuthEntry;
case AuthType.Recaptcha:

View File

@@ -26,7 +26,6 @@ import { EchoChamber } from "../../../stores/local-echo/EchoChamber";
import { RoomNotifState } from "../../../RoomNotifs";
import Modal from "../../../Modal";
import ExportDialog from "../dialogs/ExportDialog";
import { useFeatureEnabled } from "../../../hooks/useSettings";
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
@@ -261,11 +260,10 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
);
}
const pinningEnabled = useFeatureEnabled("feature_pinning");
const pinCount = usePinnedEvents(pinningEnabled ? room : undefined)?.length;
const pinCount = usePinnedEvents(room).length;
let pinsOption: JSX.Element | undefined;
if (pinningEnabled && !isVideoRoom) {
if (!isVideoRoom) {
pinsOption = (
<IconizedContextMenuOption
onClick={(ev: ButtonEvent) => {

View File

@@ -21,7 +21,6 @@ import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePha
import { ActionPayload } from "../../../dispatcher/payloads";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { showThreadPanel } from "../../../dispatcher/dispatch-actions/threads";
import SettingsStore from "../../../settings/SettingsStore";
import {
RoomNotificationStateStore,
UPDATE_STATUS_INDICATOR,
@@ -245,17 +244,16 @@ export default class LegacyRoomHeaderButtons extends HeaderButtons<IProps> {
const rightPanelPhaseButtons: Map<RightPanelPhases, any> = new Map();
if (SettingsStore.getValue("feature_pinning")) {
rightPanelPhaseButtons.set(
RightPanelPhases.PinnedMessages,
<PinnedMessagesHeaderButton
key="pinnedMessagesButton"
room={this.props.room}
isHighlighted={this.isPhase(RightPanelPhases.PinnedMessages)}
onClick={this.onPinnedMessagesClicked}
/>,
);
}
rightPanelPhaseButtons.set(
RightPanelPhases.PinnedMessages,
<PinnedMessagesHeaderButton
key="pinnedMessagesButton"
room={this.props.room}
isHighlighted={this.isPhase(RightPanelPhases.PinnedMessages)}
onClick={this.onPinnedMessagesClicked}
/>,
);
rightPanelPhaseButtons.set(
RightPanelPhases.Timeline,
<TimelineCardHeaderButton

View File

@@ -49,7 +49,6 @@ import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import { E2EStatus } from "../../../utils/ShieldUtils";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import { useFeatureEnabled } from "../../../hooks/useSettings";
import RoomName from "../elements/RoomName";
import ExportDialog from "../dialogs/ExportDialog";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
@@ -73,6 +72,7 @@ import { Key } from "../../../Keyboard";
import { useTransition } from "../../../hooks/useTransition";
import { useIsVideoRoom } from "../../../utils/video-rooms";
import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
interface IProps {
room: Room;
@@ -314,8 +314,7 @@ const RoomSummaryCard: React.FC<IProps> = ({
</header>
);
const pinningEnabled = useFeatureEnabled("feature_pinning");
const pinCount = usePinnedEvents(pinningEnabled ? room : undefined)?.length;
const pinCount = usePinnedEvents(room).length;
const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () =>
RoomListStore.instance.getTagsForRoom(room),
@@ -382,17 +381,25 @@ const RoomSummaryCard: React.FC<IProps> = ({
{!isVideoRoom && (
<>
{pinningEnabled && (
<MenuItem
Icon={PinIcon}
label={_t("right_panel|pinned_messages_button")}
onSelect={onRoomPinsClick}
>
<Text as="span" size="sm">
{pinCount}
</Text>
</MenuItem>
)}
<ReleaseAnnouncement
feature="pinningMessageList"
header={_t("right_panel|pinned_messages|release_announcement|title")}
description={_t("right_panel|pinned_messages|release_announcement|description")}
closeLabel={_t("right_panel|pinned_messages|release_announcement|close")}
placement="top"
>
<div>
<MenuItem
Icon={PinIcon}
label={_t("right_panel|pinned_messages_button")}
onSelect={onRoomPinsClick}
>
<Text as="span" size="sm">
{pinCount}
</Text>
</MenuItem>
</div>
</ReleaseAnnouncement>
<MenuItem Icon={FilesIcon} label={_t("right_panel|files_button")} onSelect={onRoomFilesClick} />
</>
)}

View File

@@ -267,15 +267,13 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
[EventType.RoomServerAcl]: _td("room_settings|permissions|m.room.server_acl"),
[EventType.Reaction]: _td("room_settings|permissions|m.reaction"),
[EventType.RoomRedaction]: _td("room_settings|permissions|m.room.redaction"),
[EventType.RoomPinnedEvents]: _td("room_settings|permissions|m.room.pinned_events"),
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
"im.vector.modular.widgets": isSpaceRoom ? null : _td("room_settings|permissions|m.widget"),
[VoiceBroadcastInfoEventType]: _td("room_settings|permissions|io.element.voice_broadcast_info"),
};
if (SettingsStore.getValue("feature_pinning")) {
plEventsToLabels[EventType.RoomPinnedEvents] = _td("room_settings|permissions|m.room.pinned_events");
}
// MSC3401: Native Group VoIP signaling
if (SettingsStore.getValue("feature_group_calls")) {
plEventsToLabels[ElementCall.CALL_EVENT_TYPE.name] = _td("room_settings|permissions|m.call");

View File

@@ -29,7 +29,7 @@ import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
*/
export const useUserTimezone = (cli: MatrixClient, userId: string): { timezone: string; friendly: string } | null => {
const [timezone, setTimezone] = useState<string>();
const [updateInterval, setUpdateInterval] = useState<number>();
const [updateInterval, setUpdateInterval] = useState<ReturnType<typeof setTimeout>>();
const [friendly, setFriendly] = useState<string>();
const [supported, setSupported] = useState<boolean>();

View File

@@ -229,6 +229,7 @@
},
"misconfigured_body": "Ask your %(brand)s admin to check <a>your config</a> for incorrect or duplicate entries.",
"misconfigured_title": "Your %(brand)s is misconfigured",
"mobile_create_account_title": "You're about to create an account on %(hsName)s",
"msisdn_field_description": "Other users can invite you to rooms using your contact details",
"msisdn_field_label": "Phone",
"msisdn_field_number_invalid": "That phone number doesn't look quite right, please check and try again",
@@ -369,6 +370,8 @@
"email_resend_prompt": "Did not receive it? <a>Resend it</a>",
"email_resent": "Resent!",
"fallback_button": "Start authentication",
"mas_cross_signing_reset_cta": "Go to your account",
"mas_cross_signing_reset_description": "Reset your identity through your account provider and then come back and click “Retry”.",
"msisdn": "A text message has been sent to %(msisdn)s",
"msisdn_token_incorrect": "Token incorrect",
"msisdn_token_prompt": "Please enter the code it contains:",
@@ -1465,7 +1468,6 @@
"notifications": "Enable the notifications panel in the room header",
"oidc_native_flow": "OIDC native authentication",
"oidc_native_flow_description": "⚠ WARNING: Experimental. Use OIDC native authentication when supported by the server.",
"pinning": "Message Pinning",
"release_announcement": "Release announcement",
"render_reaction_images": "Render custom images in reactions",
"render_reaction_images_description": "Sometimes referred to as \"custom emojis\".",
@@ -1851,6 +1853,11 @@
"other": "You can only pin up to %(count)s widgets"
},
"menu": "Open menu",
"release_announcement": {
"close": "Ok",
"description": "Find all pinned messages here. Rollover any message and select “Pin” to add it.",
"title": "All new pinned messages"
},
"reply_thread": "Reply to a <link>thread message</link>",
"title": "Pinned messages",
"unpin_all": {

View File

@@ -275,14 +275,6 @@ export const SETTINGS: { [setting: string]: ISetting } = {
supportedLevelsAreOrdered: true,
default: false,
},
"feature_pinning": {
isFeature: true,
labsGroup: LabGroup.Messaging,
displayName: _td("labs|pinning"),
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
supportedLevelsAreOrdered: true,
default: true,
},
"feature_wysiwyg_composer": {
isFeature: true,
labsGroup: LabGroup.Messaging,
@@ -876,6 +868,10 @@ export const SETTINGS: { [setting: string]: ISetting } = {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: null,
},
"Registration.mobileRegistrationHelper": {
supportedLevels: [SettingLevel.CONFIG],
default: false,
},
"autocompleteDelay": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
default: 200,

View File

@@ -17,7 +17,7 @@ import { Features } from "../settings/Settings";
/**
* The features are shown in the array order.
*/
const FEATURES = ["threadsActivityCentre"] as const;
const FEATURES = ["threadsActivityCentre", "pinningMessageList"] as const;
/**
* All the features that can be shown in the release announcements.
*/

View File

@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
import { MatrixEvent, EventType, M_POLL_START, MatrixClient, EventTimeline, Room } from "matrix-js-sdk/src/matrix";
import { isContentActionable } from "./EventUtils";
import SettingsStore from "../settings/SettingsStore";
import { ReadPinsEventId } from "../components/views/right_panel/types";
export default class PinningUtils {
@@ -70,7 +69,6 @@ export default class PinningUtils {
* @private
*/
private static canPinOrUnpin(matrixClient: MatrixClient, mxEvent: MatrixEvent): boolean {
if (!SettingsStore.getValue("feature_pinning")) return false;
if (!isContentActionable(mxEvent)) return false;
const room = matrixClient.getRoom(mxEvent.getRoomId());