Fixup labs feature

This commit is contained in:
Half-Shot 2026-04-02 18:07:00 +01:00
parent c3986a937a
commit eb356d960b
7 changed files with 120 additions and 8 deletions

View File

@ -297,7 +297,9 @@ class MatrixClientPegClass implements IMatrixClientPeg {
opts.lazyLoadMembers = true;
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
opts.threadSupport = true;
opts.unstableMSC4429SyncUserProfileFields = ["org.matrix.msc4426.status"];
if (SettingsStore.getValue("feature_user_status")) {
opts.unstableMSC4429SyncUserProfileFields = ["org.matrix.msc4426.status"];
}
if (SettingsStore.getValue("feature_sliding_sync")) {
throw new UserFriendlyError("sliding_sync_legacy_no_longer_supported");

View File

@ -11,6 +11,7 @@ import { logger as rootLogger } from "matrix-js-sdk/src/logger";
import { useMatrixClientContext } from "../contexts/MatrixClientContext";
import { useTypedEventEmitter } from "./useEventEmitter";
import { useFeatureEnabled } from "./useSettings";
const logger = rootLogger.getChild("useUserStatus");
@ -19,7 +20,15 @@ export interface UserStatus {
text: string;
}
const MAX_STATUS_TEXT_BYTES = 256;
export function userStatusTextWithinMaxLength(text: string): boolean {
const textEncoder = new TextEncoder();
return textEncoder.encode(text).length <= MAX_STATUS_TEXT_BYTES;
}
export function useUserStatus(userId: string | undefined): UserStatus | undefined {
const isEnabled = useFeatureEnabled("feature_user_status");
const matrixClient = useMatrixClientContext();
const [rawUserStatus, setRawUserStatus] = useState<unknown | undefined>();
@ -33,6 +42,9 @@ export function useUserStatus(userId: string | undefined): UserStatus | undefine
});
useEffect(() => {
(async () => {
if (!isEnabled) {
return;
}
if (!userId) {
setRawUserStatus(undefined);
return;
@ -52,23 +64,26 @@ export function useUserStatus(userId: string | undefined): UserStatus | undefine
}
}
})();
}, [userId, matrixClient]);
}, [isEnabled, userId, matrixClient]);
if (!isEnabled) {
return;
}
if (typeof rawUserStatus !== "object" || rawUserStatus === null) {
logger.warn(`value of "org.matrix.msc4426.status" was not an object for ${userId}`);
return undefined;
return;
}
if ("emoji" in rawUserStatus === false || typeof rawUserStatus.emoji !== "string" || !rawUserStatus.emoji) {
logger.warn(`"emoji" property was not a valid string for ${userId}`);
return undefined;
return;
}
if ("text" in rawUserStatus === false || typeof rawUserStatus.text !== "string" || !rawUserStatus.text) {
logger.warn(`"status" property was not a valid string for ${userId}`);
return undefined;
logger.warn(`"text" property was not a valid string for ${userId}`);
return;
}
return {
emoji: rawUserStatus.emoji,
text: rawUserStatus.text,
text: userStatusTextWithinMaxLength(rawUserStatus.text) ? rawUserStatus.text : `${rawUserStatus.text}`,
};
}

View File

@ -1530,6 +1530,11 @@
"experimental_section": "Early previews",
"extended_profiles_msc_support": "Requires your server to support MSC4133",
"feature_disable_call_per_sender_encryption": "Disable per-sender encryption for Element Call",
"feature_user_status": {
"description": "Enables being able to see and set a current status.",
"display_name": "User status",
"required_msc_support": "Requires MSC4429 (Profile Updates for Legacy Sync)"
},
"feature_wysiwyg_composer_description": "Use rich text instead of Markdown in the message composer.",
"group_calls": "New group call experience",
"group_developer": "Developer",
@ -3135,6 +3140,14 @@
"server_error_detail": "Server unavailable, overloaded, or something else went wrong.",
"shrug": "Prepends ¯\\_(ツ)_/¯ to a plain-text message",
"spoiler": "Sends the given message as a spoiler",
"status": {
"description": "Set your current status",
"no_args": "No arguments provided. You should supply an emoij and an optional text component.",
"no_emoji": "You did not provide an emoji",
"no_text": "You did not provide any status text",
"too_long_emoji": "The first argument must be an emoji",
"too_long_text": "The text you provided was too long."
},
"tableflip": "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message",
"topic": "Gets or sets the room topic",
"topic_none": "This room has no topic.",

View File

@ -1,4 +1,5 @@
/*
Copyright 2026 Element Creations Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2018-2024 The Matrix.org Foundation C.I.C.
Copyright 2017 Travis Ralston
@ -226,6 +227,7 @@ export interface Settings {
"feature_ask_to_join": IFeature;
"feature_notifications": IFeature;
"feature_msc4362_encrypted_state_events": IFeature;
"feature_user_status": IFeature;
// These are in the feature namespace but aren't actually features
"feature_hidebold": IBaseSetting<boolean>;
@ -795,6 +797,30 @@ export const SETTINGS: Settings = {
shouldWarn: true,
default: false,
},
"feature_user_status": {
isFeature: true,
labsGroup: LabGroup.Profile,
displayName: _td("labs|feature_user_status|display_name"),
description: _td("labs|feature_user_status|description"),
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
supportedLevelsAreOrdered: true,
controller: new ServerSupportUnstableFeatureController(
"feature_user_status",
defaultWatchManager,
[["org.matrix.msc4429"], ["org.matrix.msc4429.stable"]],
undefined,
_td("labs|feature_user_status|required_msc_support"),
false,
// We have to assume it's available during early startup because of a race:
// The feature is used to enable extra sync filters during MatrixClient setup
// and we can't check for serverside support until the client has finished setting up.
// Once the client has setup, (so by the time the user actually opens the labs menu) we can
// enforce proper checks.
true,
true,
),
default: false,
},
"useCompactLayout": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
displayName: _td("settings|preferences|compact_modern"),

View File

@ -1,4 +1,5 @@
/*
Copyright 2026 Element Creations Ltd.
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
@ -12,6 +13,7 @@ import { type WatchManager } from "../WatchManager";
import SettingsStore from "../SettingsStore";
import { type SettingKey } from "../Settings.tsx";
import { _t } from "../../languageHandler.tsx";
import PlatformPeg from "../../PlatformPeg.ts";
/**
* Disables a given setting if the server unstable feature it requires is not supported
@ -28,6 +30,9 @@ export default class ServerSupportUnstableFeatureController extends MatrixClient
* @param unstableFeatureGroups - If any one of the feature groups is satisfied,
* then the setting is considered enabled. A feature group is satisfied if all of
* the features in the group are supported (all features in a group are required).
* @param defaultEnabled - If we haven't been able to check for support yet, should
* this feature be enabled or disabled (default).
* @param forceReload - Should the client force reload.
*/
public constructor(
private readonly settingName: SettingKey,
@ -36,12 +41,23 @@ export default class ServerSupportUnstableFeatureController extends MatrixClient
private readonly stableVersion?: string,
private readonly disabledMessage?: TranslationKey,
private readonly forcedValue: any = false,
private readonly defaultEnabled = false,
private readonly forceReload = false,
) {
super();
}
public async onChange(): Promise<void> {
if (this.forceReload) {
PlatformPeg.get()?.reload();
}
}
public get disabled(): boolean {
return !this.enabled;
if (this.enabled !== undefined) {
return !this.enabled;
}
return !this.defaultEnabled;
}
public set disabled(newDisabledValue: boolean) {

View File

@ -1,4 +1,5 @@
/*
Copyright 2026 Element Creations Ltd.
Copyright 2024 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
@ -62,6 +63,7 @@ import { goto, join } from "./join";
import { manuallyVerifyDevice } from "../components/views/dialogs/ManualDeviceKeyVerificationDialog";
import upgraderoom from "./upgraderoom/upgraderoom";
import { emoticon } from "./emoticon";
import { userStatusTextWithinMaxLength } from "../hooks/useUserStatus";
export { CommandCategories, Command };
@ -819,6 +821,39 @@ export const Commands = [
},
renderingTypes: [TimelineRenderingType.Room],
}),
new Command({
command: "status",
args: "<emoji> <text>",
description: _td("slash_command|status|description"),
isEnabled: () => SettingsStore.getValue("feature_user_status"),
runFn: function (cli, _roomId, _threadId, args) {
if (!args) {
return reject(new UserFriendlyError("slash_command|status|no_args"));
}
const [emojiText, text] = splitAtFirstSpace(args);
if (!emojiText) {
return reject(new UserFriendlyError("slash_command|status|no_emoji"));
}
if (!text) {
return reject(new UserFriendlyError("slash_command|status|no_text"));
}
const [emoji, additionalSegment] = [...new Intl.Segmenter().segment(emojiText)];
if (additionalSegment) {
return reject(new UserFriendlyError("slash_command|status|too_long_emoji"));
}
if (text && !userStatusTextWithinMaxLength(text)) {
return reject(new UserFriendlyError("slash_command|status|too_long_text"));
}
return success(
cli.setExtendedProfileProperty("org.matrix.msc4426.status", {
emoji: emoji.segment,
text,
}),
);
},
category: CommandCategories.actions,
renderingTypes: [TimelineRenderingType.Room],
}),
// Command definitions for autocompletion ONLY:
// /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes

View File

@ -139,5 +139,10 @@ joining a room.
Replaces the legacy notification settings with a new one to manage push rules.
## User status (`feature_user_status`)
Enables setting a status message in your profile and to be able to view other's statuses.
Requires [MSC4429](https://github.com/matrix-org/matrix-spec-proposals/pull/4429) and [MSC4426](https://github.com/matrix-org/matrix-spec-proposals/pull/4426).
**Warning** This feature has options which are not backwards compatible, disabling
it may have unintended consequences.