Merge pull request #2347 from matrix-org/travis/custom-status

Custom status messages
This commit is contained in:
Travis Ralston 2018-12-19 11:11:15 -07:00 committed by GitHub
commit 2e1e536d5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 396 additions and 5 deletions

View file

@ -24,8 +24,10 @@
@import "./structures/_ViewSource.scss"; @import "./structures/_ViewSource.scss";
@import "./structures/login/_Login.scss"; @import "./structures/login/_Login.scss";
@import "./views/avatars/_BaseAvatar.scss"; @import "./views/avatars/_BaseAvatar.scss";
@import "./views/avatars/_MemberStatusMessageAvatar.scss";
@import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/context_menus/_MessageContextMenu.scss";
@import "./views/context_menus/_RoomTileContextMenu.scss"; @import "./views/context_menus/_RoomTileContextMenu.scss";
@import "./views/context_menus/_StatusMessageContextMenu.scss";
@import "./views/context_menus/_TagTileContextMenu.scss"; @import "./views/context_menus/_TagTileContextMenu.scss";
@import "./views/dialogs/_BugReportDialog.scss"; @import "./views/dialogs/_BugReportDialog.scss";
@import "./views/dialogs/_ChangelogDialog.scss"; @import "./views/dialogs/_ChangelogDialog.scss";

View file

@ -0,0 +1,20 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MemberStatusMessageAvatar_hasStatus {
border: 2px solid $accent-color;
border-radius: 40px;
}

View file

@ -0,0 +1,55 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_StatusMessageContextMenu_message {
display: inline-block;
border-radius: 3px 0 0 3px;
border: 1px solid $input-border-color;
font-size: 13px;
padding: 7px 7px 7px 9px;
width: 135px;
background-color: $primary-bg-color !important;
}
.mx_StatusMessageContextMenu_submit {
display: inline-block;
}
.mx_StatusMessageContextMenu_submitFaded {
opacity: 0.5;
}
.mx_StatusMessageContextMenu_submit img {
vertical-align: middle;
margin-left: 8px;
}
.mx_StatusMessageContextMenu hr {
border: 0.5px solid $menu-border-color;
}
.mx_StatusMessageContextMenu_clearIcon {
margin: 5px 15px 5px 5px;
vertical-align: middle;
}
.mx_StatusMessageContextMenu_clear {
padding: 2px;
}
.mx_StatusMessageContextMenu_hasStatus .mx_StatusMessageContextMenu_clear {
color: $warning-color;
}

View file

@ -111,4 +111,12 @@ limitations under the License.
opacity: 0.25; opacity: 0.25;
} }
.mx_EntityTile_subtext {
font-size: 11px;
opacity: 0.5;
overflow: hidden;
white-space: nowrap;
text-overflow: clip;
}

View file

@ -110,3 +110,10 @@ limitations under the License.
margin-left: 8px; margin-left: 8px;
} }
.mx_MemberInfo_statusMessage {
font-size: 11px;
opacity: 0.5;
overflow: hidden;
white-space: nowrap;
text-overflow: clip;
}

View file

@ -35,7 +35,19 @@ limitations under the License.
.mx_RoomTile_nameContainer { .mx_RoomTile_nameContainer {
display: inline-block; display: inline-block;
width: 180px; width: 180px;
height: 24px; vertical-align: middle;
}
.mx_RoomTile_subtext {
display: inline-block;
font-size: 11px;
padding: 0 0 0 7px;
margin: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: clip;
position: relative;
bottom: 4px;
} }
.mx_RoomTile_avatar_container { .mx_RoomTile_avatar_container {
@ -49,10 +61,14 @@ limitations under the License.
padding-left: 16px; padding-left: 16px;
padding-right: 6px; padding-right: 6px;
width: 24px; width: 24px;
height: 24px;
vertical-align: middle; vertical-align: middle;
} }
.mx_RoomTile_hasSubtext .mx_RoomTile_avatar {
padding-top: 0;
vertical-align: super;
}
.mx_RoomTile_dm { .mx_RoomTile_dm {
display: block; display: block;
position: absolute; position: absolute;

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
<title>Tick</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Custom-Status-Copy" transform="translate(-529.000000, -917.000000)" fill-rule="nonzero">
<g id="Tick" transform="translate(530.000000, 918.000000)">
<circle id="Oval" stroke="#6AAC8C" fill="#75CFA6" cx="9" cy="9" r="9"></circle>
<g id="Glyph" transform="translate(8.949747, 7.949747) rotate(-45.000000) translate(-8.949747, -7.949747) translate(4.449747, 5.449747)" fill="#FFFFFF">
<rect id="Rectangle" x="0" y="0" width="2" height="5"></rect>
<rect id="Rectangle" x="0" y="3" width="9" height="2"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,120 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import MatrixClientPeg from '../../../MatrixClientPeg';
import AccessibleButton from '../elements/AccessibleButton';
import MemberAvatar from '../avatars/MemberAvatar';
import classNames from 'classnames';
import * as ContextualMenu from "../../structures/ContextualMenu";
import StatusMessageContextMenu from "../context_menus/StatusMessageContextMenu";
import SettingsStore from "../../../settings/SettingsStore";
export default class MemberStatusMessageAvatar extends React.Component {
static propTypes = {
member: PropTypes.object.isRequired,
width: PropTypes.number,
height: PropTypes.number,
resizeMethod: PropTypes.string,
};
static defaultProps = {
width: 40,
height: 40,
resizeMethod: 'crop',
};
constructor(props, context) {
super(props, context);
}
componentWillMount() {
if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) {
throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user");
}
}
componentDidMount() {
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
if (this.props.member.user) {
this.setState({message: this.props.member.user._unstable_statusMessage});
} else {
this.setState({message: ""});
}
}
componentWillUnmount() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents);
}
}
_onRoomStateEvents = (ev, state) => {
if (ev.getStateKey() !== MatrixClientPeg.get().getUserId()) return;
if (ev.getType() !== "im.vector.user_status") return;
// TODO: We should be relying on `this.props.member.user._unstable_statusMessage`
// We don't currently because the js-sdk doesn't emit a specific event for this
// change, and we don't want to race it. This should be improved when we rip out
// the im.vector.user_status stuff and replace it with a complete solution.
this.setState({message: ev.getContent()["status"]});
};
_onClick = (e) => {
e.stopPropagation();
const elementRect = e.target.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
const x = (elementRect.left + window.pageXOffset) - (elementRect.width / 2) + 3;
const chevronOffset = 12;
let y = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
y = y - (chevronOffset + 4); // where 4 is 1/4 the height of the chevron
ContextualMenu.createMenu(StatusMessageContextMenu, {
chevronOffset: chevronOffset,
chevronFace: 'bottom',
left: x,
top: y,
menuWidth: 190,
user: this.props.member.user,
});
};
render() {
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
return <MemberAvatar member={this.props.member}
width={this.props.width}
height={this.props.height}
resizeMethod={this.props.resizeMethod} />;
}
const hasStatus = this.props.member.user ? !!this.props.member.user._unstable_statusMessage : false;
const classes = classNames({
"mx_MemberStatusMessageAvatar": true,
"mx_MemberStatusMessageAvatar_hasStatus": hasStatus,
});
return <AccessibleButton onClick={this._onClick} className={classes} element="div">
<MemberAvatar member={this.props.member}
width={this.props.width}
height={this.props.height}
resizeMethod={this.props.resizeMethod} />
</AccessibleButton>;
}
}

View file

@ -0,0 +1,86 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
import AccessibleButton from '../elements/AccessibleButton';
import classNames from 'classnames';
export default class StatusMessageContextMenu extends React.Component {
static propTypes = {
// js-sdk User object. Not required because it might not exist.
user: PropTypes.object,
};
constructor(props, context) {
super(props, context);
this.state = {
message: props.user ? props.user._unstable_statusMessage : "",
};
}
_onClearClick = async(e) => {
await MatrixClientPeg.get()._unstable_setStatusMessage("");
this.setState({message: ""});
};
_onSubmit = (e) => {
e.preventDefault();
MatrixClientPeg.get()._unstable_setStatusMessage(this.state.message);
};
_onStatusChange = (e) => {
this.setState({message: e.target.value});
};
render() {
const formSubmitClasses = classNames({
"mx_StatusMessageContextMenu_submit": true,
"mx_StatusMessageContextMenu_submitFaded": !this.state.message, // no message == faded
});
const form = <form className="mx_StatusMessageContextMenu_form" onSubmit={this._onSubmit} autoComplete="off">
<input type="text" key="message" placeholder={_t("Set a new status...")} autoFocus={true}
className="mx_StatusMessageContextMenu_message"
value={this.state.message} onChange={this._onStatusChange} maxLength="60" />
<AccessibleButton onClick={this._onSubmit} element="div" className={formSubmitClasses}>
<img src="img/icons-checkmark.svg" width="22" height="22" />
</AccessibleButton>
</form>;
const clearIcon = this.state.message ? "img/cancel-red.svg" : "img/cancel.svg";
const clearButton = <AccessibleButton onClick={this._onClearClick} disabled={!this.state.message}
className="mx_StatusMessageContextMenu_clear">
<img src={clearIcon} alt={_t('Clear status')} width="12" height="12"
className="mx_filterFlipColor mx_StatusMessageContextMenu_clearIcon" />
<span>{_t("Clear status")}</span>
</AccessibleButton>;
const menuClasses = classNames({
"mx_StatusMessageContextMenu": true,
"mx_StatusMessageContextMenu_hasStatus": this.state.message,
});
return <div className={menuClasses}>
{ form }
<hr />
{ clearButton }
</div>;
}
}

View file

@ -70,6 +70,7 @@ const EntityTile = React.createClass({
onClick: PropTypes.func, onClick: PropTypes.func,
suppressOnHover: PropTypes.bool, suppressOnHover: PropTypes.bool,
showPresence: PropTypes.bool, showPresence: PropTypes.bool,
subtextLabel: PropTypes.string,
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -129,6 +130,9 @@ const EntityTile = React.createClass({
presenceState={this.props.presenceState} />; presenceState={this.props.presenceState} />;
nameClasses += ' mx_EntityTile_name_hover'; nameClasses += ' mx_EntityTile_name_hover';
} }
if (this.props.subtextLabel) {
presenceLabel = <span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>;
}
nameEl = ( nameEl = (
<div className="mx_EntityTile_details"> <div className="mx_EntityTile_details">
<img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12" /> <img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12" />
@ -138,6 +142,15 @@ const EntityTile = React.createClass({
{presenceLabel} {presenceLabel}
</div> </div>
); );
} else if (this.props.subtextLabel) {
nameEl = (
<div className="mx_EntityTile_details">
<EmojiText element="div" className="mx_EntityTile_name" dir="auto">
{name}
</EmojiText>
<span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>
</div>
);
} else { } else {
nameEl = ( nameEl = (
<EmojiText element="div" className="mx_EntityTile_name" dir="auto">{ name }</EmojiText> <EmojiText element="div" className="mx_EntityTile_name" dir="auto">{ name }</EmojiText>

View file

@ -42,6 +42,7 @@ import AccessibleButton from '../elements/AccessibleButton';
import RoomViewStore from '../../../stores/RoomViewStore'; import RoomViewStore from '../../../stores/RoomViewStore';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
import MultiInviter from "../../../utils/MultiInviter"; import MultiInviter from "../../../utils/MultiInviter";
import SettingsStore from "../../../settings/SettingsStore";
module.exports = withMatrixClient(React.createClass({ module.exports = withMatrixClient(React.createClass({
displayName: 'MemberInfo', displayName: 'MemberInfo',
@ -889,11 +890,16 @@ module.exports = withMatrixClient(React.createClass({
let presenceState; let presenceState;
let presenceLastActiveAgo; let presenceLastActiveAgo;
let presenceCurrentlyActive; let presenceCurrentlyActive;
let statusMessage;
if (this.props.member.user) { if (this.props.member.user) {
presenceState = this.props.member.user.presence; presenceState = this.props.member.user.presence;
presenceLastActiveAgo = this.props.member.user.lastActiveAgo; presenceLastActiveAgo = this.props.member.user.lastActiveAgo;
presenceCurrentlyActive = this.props.member.user.currentlyActive; presenceCurrentlyActive = this.props.member.user.currentlyActive;
if (SettingsStore.isFeatureEnabled("feature_custom_status")) {
statusMessage = this.props.member.user._unstable_statusMessage;
}
} }
const room = this.props.matrixClient.getRoom(this.props.member.roomId); const room = this.props.matrixClient.getRoom(this.props.member.roomId);
@ -915,6 +921,11 @@ module.exports = withMatrixClient(React.createClass({
presenceState={presenceState} />; presenceState={presenceState} />;
} }
let statusLabel = null;
if (statusMessage) {
statusLabel = <span className="mx_MemberInfo_statusMessage">{ statusMessage }</span>;
}
let roomMemberDetails = null; let roomMemberDetails = null;
if (this.props.member.roomId) { // is in room if (this.props.member.roomId) { // is in room
const PowerSelector = sdk.getComponent('elements.PowerSelector'); const PowerSelector = sdk.getComponent('elements.PowerSelector');
@ -931,6 +942,7 @@ module.exports = withMatrixClient(React.createClass({
</div> </div>
<div className="mx_MemberInfo_profileField"> <div className="mx_MemberInfo_profileField">
{presenceLabel} {presenceLabel}
{statusLabel}
</div> </div>
</div>; </div>;
} }

View file

@ -16,6 +16,8 @@ limitations under the License.
'use strict'; 'use strict';
import SettingsStore from "../../../settings/SettingsStore";
const React = require('react'); const React = require('react');
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -85,6 +87,11 @@ module.exports = React.createClass({
const active = -1; const active = -1;
const presenceState = member.user ? member.user.presence : null; const presenceState = member.user ? member.user.presence : null;
let statusMessage = null;
if (member.user && SettingsStore.isFeatureEnabled("feature_custom_status")) {
statusMessage = member.user._unstable_statusMessage;
}
const av = ( const av = (
<MemberAvatar member={member} width={36} height={36} /> <MemberAvatar member={member} width={36} height={36} />
); );
@ -106,7 +113,9 @@ module.exports = React.createClass({
presenceLastTs={member.user ? member.user.lastPresenceTs : 0} presenceLastTs={member.user ? member.user.lastPresenceTs : 0}
presenceCurrentlyActive={member.user ? member.user.currentlyActive : false} presenceCurrentlyActive={member.user ? member.user.currentlyActive : false}
avatarJsx={av} title={this.getPowerLabel()} onClick={this.onClick} avatarJsx={av} title={this.getPowerLabel()} onClick={this.onClick}
name={name} powerStatus={powerStatus} showPresence={this.props.showPresence} /> name={name} powerStatus={powerStatus} showPresence={this.props.showPresence}
subtextLabel={statusMessage}
/>
); );
}, },
}); });

View file

@ -291,7 +291,7 @@ export default class MessageComposer extends React.Component {
render() { render() {
const uploadInputStyle = {display: 'none'}; const uploadInputStyle = {display: 'none'};
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
const TintableSvg = sdk.getComponent("elements.TintableSvg"); const TintableSvg = sdk.getComponent("elements.TintableSvg");
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
@ -300,7 +300,7 @@ export default class MessageComposer extends React.Component {
if (this.state.me) { if (this.state.me) {
controls.push( controls.push(
<div key="controls_avatar" className="mx_MessageComposer_avatar"> <div key="controls_avatar" className="mx_MessageComposer_avatar">
<MemberAvatar member={this.state.me} width={24} height={24} /> <MemberStatusMessageAvatar member={this.state.me} width={24} height={24} />
</div>, </div>,
); );
} }

View file

@ -30,6 +30,7 @@ import * as FormattingUtils from '../../../utils/FormattingUtils';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import ActiveRoomObserver from '../../../ActiveRoomObserver'; import ActiveRoomObserver from '../../../ActiveRoomObserver';
import RoomViewStore from '../../../stores/RoomViewStore'; import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore from "../../../settings/SettingsStore";
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'RoomTile', displayName: 'RoomTile',
@ -251,6 +252,17 @@ module.exports = React.createClass({
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge(); const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
const badges = notifBadges || mentionBadges; const badges = notifBadges || mentionBadges;
const isJoined = this.props.room.getMyMembership() === "join";
const looksLikeDm = this.props.room.getInvitedAndJoinedMemberCount() === 2;
let subtext = null;
if (!isInvite && isJoined && looksLikeDm && SettingsStore.isFeatureEnabled("feature_custom_status")) {
const selfId = MatrixClientPeg.get().getUserId();
const otherMember = this.props.room.currentState.getMembersExcept([selfId])[0];
if (otherMember && otherMember.user && otherMember.user._unstable_statusMessage) {
subtext = otherMember.user._unstable_statusMessage;
}
}
const classes = classNames({ const classes = classNames({
'mx_RoomTile': true, 'mx_RoomTile': true,
'mx_RoomTile_selected': this.state.selected, 'mx_RoomTile_selected': this.state.selected,
@ -261,6 +273,7 @@ module.exports = React.createClass({
'mx_RoomTile_menuDisplayed': this.state.menuDisplayed, 'mx_RoomTile_menuDisplayed': this.state.menuDisplayed,
'mx_RoomTile_noBadges': !badges, 'mx_RoomTile_noBadges': !badges,
'mx_RoomTile_transparent': this.props.transparent, 'mx_RoomTile_transparent': this.props.transparent,
'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
}); });
const avatarClasses = classNames({ const avatarClasses = classNames({
@ -291,6 +304,7 @@ module.exports = React.createClass({
const EmojiText = sdk.getComponent('elements.EmojiText'); const EmojiText = sdk.getComponent('elements.EmojiText');
let label; let label;
let subtextLabel;
let tooltip; let tooltip;
if (!this.props.collapsed) { if (!this.props.collapsed) {
const nameClasses = classNames({ const nameClasses = classNames({
@ -299,6 +313,8 @@ module.exports = React.createClass({
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed, 'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
}); });
subtextLabel = subtext ? <span className="mx_RoomTile_subtext">{ subtext }</span> : null;
if (this.state.selected) { if (this.state.selected) {
const nameSelected = <EmojiText>{ name }</EmojiText>; const nameSelected = <EmojiText>{ name }</EmojiText>;
@ -339,6 +355,7 @@ module.exports = React.createClass({
</div> </div>
<div className="mx_RoomTile_nameContainer"> <div className="mx_RoomTile_nameContainer">
{ label } { label }
{ subtextLabel }
{ badge } { badge }
</div> </div>
{ /* { incomingCallBox } */ } { /* { incomingCallBox } */ }

View file

@ -257,6 +257,7 @@
"Please contact your homeserver administrator.": "Please contact your homeserver administrator.", "Please contact your homeserver administrator.": "Please contact your homeserver administrator.",
"Failed to join room": "Failed to join room", "Failed to join room": "Failed to join room",
"Message Pinning": "Message Pinning", "Message Pinning": "Message Pinning",
"Custom user status messages": "Custom user status messages",
"Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view", "Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view",
"Backup of encryption keys to server": "Backup of encryption keys to server", "Backup of encryption keys to server": "Backup of encryption keys to server",
"Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing", "Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing",
@ -1061,6 +1062,8 @@
"Forget": "Forget", "Forget": "Forget",
"Low Priority": "Low Priority", "Low Priority": "Low Priority",
"Direct Chat": "Direct Chat", "Direct Chat": "Direct Chat",
"Set a new status...": "Set a new status...",
"Clear status": "Clear status",
"View Community": "View Community", "View Community": "View Community",
"Sorry, your browser is <b>not</b> able to run Riot.": "Sorry, your browser is <b>not</b> able to run Riot.", "Sorry, your browser is <b>not</b> able to run Riot.": "Sorry, your browser is <b>not</b> able to run Riot.",
"Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.", "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.",

View file

@ -83,6 +83,12 @@ export const SETTINGS = {
supportedLevels: LEVELS_FEATURE, supportedLevels: LEVELS_FEATURE,
default: false, default: false,
}, },
"feature_custom_status": {
isFeature: true,
displayName: _td("Custom user status messages"),
supportedLevels: LEVELS_FEATURE,
default: false,
},
"feature_lazyloading": { "feature_lazyloading": {
isFeature: true, isFeature: true,
displayName: _td("Increase performance by only loading room members on first view"), displayName: _td("Increase performance by only loading room members on first view"),