Merge remote-tracking branch 'origin' into posthog-analytics
This commit is contained in:
commit
bd7e2dee3d
487 changed files with 11710 additions and 6874 deletions
|
@ -116,8 +116,8 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
|
|||
"to the new version of the room.</i> We'll post a link to the new room in the old " +
|
||||
"version of the room - room members will have to click this link to join the new room.",
|
||||
{}, {
|
||||
"b": (sub) => <b>{sub}</b>,
|
||||
"i": (sub) => <i>{sub}</i>,
|
||||
"b": (sub) => <b>{ sub }</b>,
|
||||
"i": (sub) => <i>{ sub }</i>,
|
||||
},
|
||||
) }
|
||||
</p>
|
||||
|
|
|
@ -61,36 +61,36 @@ export default class BridgeSettingsTab extends React.Component<IProps> {
|
|||
let content: JSX.Element;
|
||||
if (bridgeEvents.length > 0) {
|
||||
content = <div>
|
||||
<p>{_t(
|
||||
<p>{ _t(
|
||||
"This room is bridging messages to the following platforms. " +
|
||||
"<a>Learn more.</a>", {},
|
||||
{
|
||||
// TODO: We don't have this link yet: this will prevent the translators
|
||||
// having to re-translate the string when we do.
|
||||
a: sub => <a href={BRIDGES_LINK} target="_blank" rel="noreferrer noopener">{sub}</a>,
|
||||
a: sub => <a href={BRIDGES_LINK} target="_blank" rel="noreferrer noopener">{ sub }</a>,
|
||||
},
|
||||
)}</p>
|
||||
) }</p>
|
||||
<ul className="mx_RoomSettingsDialog_BridgeList">
|
||||
{ bridgeEvents.map((event) => this.renderBridgeCard(event, room)) }
|
||||
</ul>
|
||||
</div>;
|
||||
} else {
|
||||
content = <p>{_t(
|
||||
content = <p>{ _t(
|
||||
"This room isn’t bridging messages to any platforms. " +
|
||||
"<a>Learn more.</a>", {},
|
||||
{
|
||||
// TODO: We don't have this link yet: this will prevent the translators
|
||||
// having to re-translate the string when we do.
|
||||
a: sub => <a href={BRIDGES_LINK} target="_blank" rel="noreferrer noopener">{sub}</a>,
|
||||
a: sub => <a href={BRIDGES_LINK} target="_blank" rel="noreferrer noopener">{ sub }</a>,
|
||||
},
|
||||
)}</p>;
|
||||
) }</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Bridges")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Bridges") }</div>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
{content}
|
||||
{ content }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -65,7 +65,7 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
|||
const groupsEvent = room.currentState.getStateEvents("m.room.related_groups", "");
|
||||
|
||||
let urlPreviewSettings = <>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("URL Previews")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("URL Previews") }</span>
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<UrlPreviewSettings room={room} />
|
||||
</div>
|
||||
|
@ -77,7 +77,7 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
|||
let flairSection;
|
||||
if (SettingsStore.getValue(UIFeature.Flair)) {
|
||||
flairSection = <>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Flair")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Flair") }</span>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<RelatedGroupSettings
|
||||
roomId={room.roomId}
|
||||
|
@ -90,22 +90,25 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
|||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_GeneralRoomSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("General") }</div>
|
||||
<div className='mx_SettingsTab_section mx_GeneralRoomSettingsTab_profileSection'>
|
||||
<RoomProfileSettings roomId={this.props.roomId} />
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_heading">{_t("Room Addresses")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Room Addresses") }</div>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<AliasSettings roomId={this.props.roomId}
|
||||
canSetCanonicalAlias={canSetCanonical} canSetAliases={canSetAliases}
|
||||
canonicalAliasEvent={canonicalAliasEv} />
|
||||
<AliasSettings
|
||||
roomId={this.props.roomId}
|
||||
canSetCanonicalAlias={canSetCanonical}
|
||||
canSetAliases={canSetAliases}
|
||||
canonicalAliasEvent={canonicalAliasEv}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_SettingsTab_heading">{_t("Other")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Other") }</div>
|
||||
{ flairSection }
|
||||
{ urlPreviewSettings }
|
||||
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Leave room")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Leave room") }</span>
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<AccessibleButton kind='danger' onClick={this._onLeaveClick}>
|
||||
{ _t('Leave room') }
|
||||
|
|
|
@ -142,36 +142,36 @@ export default class NotificationsSettingsTab extends React.Component {
|
|||
if (this.state.uploadedFile) {
|
||||
currentUploadedFile = (
|
||||
<div>
|
||||
<span>{_t("Uploaded sound")}: <code>{this.state.uploadedFile.name}</code></span>
|
||||
<span>{ _t("Uploaded sound") }: <code>{ this.state.uploadedFile.name }</code></span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Notifications")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Notifications") }</div>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Sounds")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Sounds") }</span>
|
||||
<div>
|
||||
<span>{_t("Notification sound")}: <code>{this.state.currentSound}</code></span><br />
|
||||
<span>{ _t("Notification sound") }: <code>{ this.state.currentSound }</code></span><br />
|
||||
<AccessibleButton className="mx_NotificationSound_resetSound" disabled={this.state.currentSound == "default"} onClick={this._clearSound.bind(this)} kind="primary">
|
||||
{_t("Reset")}
|
||||
{ _t("Reset") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<div>
|
||||
<h3>{_t("Set a new custom sound")}</h3>
|
||||
<h3>{ _t("Set a new custom sound") }</h3>
|
||||
<form autoComplete="off" noValidate={true}>
|
||||
<input ref={this._soundUpload} className="mx_NotificationSound_soundUpload" type="file" onChange={this._onSoundUploadChanged.bind(this)} accept="audio/*" />
|
||||
</form>
|
||||
|
||||
{currentUploadedFile}
|
||||
{ currentUploadedFile }
|
||||
|
||||
<AccessibleButton className="mx_NotificationSound_browse" onClick={this._triggerUploader.bind(this)} kind="primary">
|
||||
{_t("Browse")}
|
||||
{ _t("Browse") }
|
||||
</AccessibleButton>
|
||||
|
||||
<AccessibleButton className="mx_NotificationSound_save" disabled={this.state.uploadedFile == null} onClick={this._onClickSaveSound.bind(this)} kind="primary">
|
||||
{_t("Save")}
|
||||
{ _t("Save") }
|
||||
</AccessibleButton>
|
||||
<br />
|
||||
</div>
|
||||
|
|
|
@ -102,10 +102,10 @@ export class BannedUser extends React.Component<IBannedUserProps> {
|
|||
const userId = this.props.member.name === this.props.member.userId ? null : this.props.member.userId;
|
||||
return (
|
||||
<li>
|
||||
{unbanButton}
|
||||
{ unbanButton }
|
||||
<span title={_t("Banned by %(displayName)s", { displayName: this.props.by })}>
|
||||
<strong>{ this.props.member.name }</strong> {userId}
|
||||
{this.props.reason ? " " + _t('Reason') + ": " + this.props.reason : ""}
|
||||
<strong>{ this.props.member.name }</strong> { userId }
|
||||
{ this.props.reason ? " " + _t('Reason') + ": " + this.props.reason : "" }
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
|
@ -273,7 +273,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
|||
parseIntWithDefault(plContent.events_default, powerLevelDescriptors.events_default.defaultValue),
|
||||
);
|
||||
|
||||
let privilegedUsersSection = <div>{_t('No users have specific privileges in this room')}</div>;
|
||||
let privilegedUsersSection = <div>{ _t('No users have specific privileges in this room') }</div>;
|
||||
let mutedUsersSection;
|
||||
if (Object.keys(userLevels).length) {
|
||||
const privilegedUsers = [];
|
||||
|
@ -320,14 +320,14 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
|||
privilegedUsersSection =
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<div className='mx_SettingsTab_subheading'>{ _t('Privileged Users') }</div>
|
||||
{privilegedUsers}
|
||||
{ privilegedUsers }
|
||||
</div>;
|
||||
}
|
||||
if (mutedUsers.length) {
|
||||
mutedUsersSection =
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<div className='mx_SettingsTab_subheading'>{ _t('Muted Users') }</div>
|
||||
{mutedUsers}
|
||||
{ mutedUsers }
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
@ -340,18 +340,21 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
|||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<div className='mx_SettingsTab_subheading'>{ _t('Banned users') }</div>
|
||||
<ul>
|
||||
{banned.map((member) => {
|
||||
{ banned.map((member) => {
|
||||
const banEvent = member.events.member.getContent();
|
||||
const sender = room.getMember(member.events.member.getSender());
|
||||
let bannedBy = member.events.member.getSender(); // start by falling back to mxid
|
||||
if (sender) bannedBy = sender.name;
|
||||
return (
|
||||
<BannedUser key={member.userId} canUnban={canBanUsers}
|
||||
member={member} reason={banEvent.reason}
|
||||
<BannedUser
|
||||
key={member.userId}
|
||||
canUnban={canBanUsers}
|
||||
member={member}
|
||||
reason={banEvent.reason}
|
||||
by={bannedBy}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
}) }
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
@ -409,15 +412,15 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
|||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_RolesRoomSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Roles & Permissions")}</div>
|
||||
{privilegedUsersSection}
|
||||
{mutedUsersSection}
|
||||
{bannedUsersSection}
|
||||
<div className="mx_SettingsTab_heading">{ _t("Roles & Permissions") }</div>
|
||||
{ privilegedUsersSection }
|
||||
{ mutedUsersSection }
|
||||
{ bannedUsersSection }
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Permissions")}</span>
|
||||
<p>{_t('Select the roles required to change various parts of the room')}</p>
|
||||
{powerSelectors}
|
||||
{eventPowerSelectors}
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Permissions") }</span>
|
||||
<p>{ _t('Select the roles required to change various parts of the room') }</p>
|
||||
{ powerSelectors }
|
||||
{ eventPowerSelectors }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -15,52 +15,43 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { GuestAccess, HistoryVisibility, JoinRule, RestrictedAllowType } from "matrix-js-sdk/src/@types/partials";
|
||||
import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { EventType } from 'matrix-js-sdk/src/@types/event';
|
||||
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||
import Modal from "../../../../../Modal";
|
||||
import QuestionDialog from "../../../dialogs/QuestionDialog";
|
||||
import StyledRadioGroup from '../../../elements/StyledRadioGroup';
|
||||
import StyledRadioGroup, { IDefinition } from '../../../elements/StyledRadioGroup';
|
||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import { UIFeature } from "../../../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import SpaceStore from "../../../../../stores/SpaceStore";
|
||||
import RoomAvatar from "../../../avatars/RoomAvatar";
|
||||
import ManageRestrictedJoinRuleDialog from '../../../dialogs/ManageRestrictedJoinRuleDialog';
|
||||
import RoomUpgradeWarningDialog from '../../../dialogs/RoomUpgradeWarningDialog';
|
||||
import { upgradeRoom } from "../../../../../utils/RoomUpgrade";
|
||||
import { arrayHasDiff } from "../../../../../utils/arrays";
|
||||
import SettingsFlag from '../../../elements/SettingsFlag';
|
||||
|
||||
// Knock and private are reserved keywords which are not yet implemented.
|
||||
export enum JoinRule {
|
||||
Public = "public",
|
||||
Knock = "knock",
|
||||
Invite = "invite",
|
||||
/**
|
||||
* @deprecated Reserved. Should not be used.
|
||||
*/
|
||||
Private = "private",
|
||||
}
|
||||
|
||||
export enum GuestAccess {
|
||||
CanJoin = "can_join",
|
||||
Forbidden = "forbidden",
|
||||
}
|
||||
|
||||
export enum HistoryVisibility {
|
||||
Invited = "invited",
|
||||
Joined = "joined",
|
||||
Shared = "shared",
|
||||
WorldReadable = "world_readable",
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
joinRule: JoinRule;
|
||||
restrictedAllowRoomIds?: string[];
|
||||
guestAccess: GuestAccess;
|
||||
history: HistoryVisibility;
|
||||
hasAliases: boolean;
|
||||
encrypted: boolean;
|
||||
roomSupportsRestricted?: boolean;
|
||||
preferredRestrictionVersion?: string;
|
||||
showAdvancedSection: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.settings.tabs.room.SecurityRoomSettingsTab")
|
||||
|
@ -70,44 +61,58 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
|
||||
this.state = {
|
||||
joinRule: JoinRule.Invite,
|
||||
guestAccess: GuestAccess.CanJoin,
|
||||
guestAccess: GuestAccess.Forbidden,
|
||||
history: HistoryVisibility.Shared,
|
||||
hasAliases: false,
|
||||
encrypted: false,
|
||||
showAdvancedSection: false,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Move this to constructor
|
||||
async UNSAFE_componentWillMount() { // eslint-disable-line camelcase
|
||||
MatrixClientPeg.get().on("RoomState.events", this.onStateEvent);
|
||||
UNSAFE_componentWillMount() { // eslint-disable-line
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on("RoomState.events", this.onStateEvent);
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
const room = cli.getRoom(this.props.roomId);
|
||||
const state = room.currentState;
|
||||
|
||||
const joinRule: JoinRule = this.pullContentPropertyFromEvent(
|
||||
state.getStateEvents("m.room.join_rules", ""),
|
||||
const joinRuleEvent = state.getStateEvents(EventType.RoomJoinRules, "");
|
||||
const joinRule: JoinRule = this.pullContentPropertyFromEvent<JoinRule>(
|
||||
joinRuleEvent,
|
||||
'join_rule',
|
||||
JoinRule.Invite,
|
||||
);
|
||||
const guestAccess: GuestAccess = this.pullContentPropertyFromEvent(
|
||||
state.getStateEvents("m.room.guest_access", ""),
|
||||
const restrictedAllowRoomIds = joinRule === JoinRule.Restricted
|
||||
? joinRuleEvent?.getContent().allow
|
||||
?.filter(a => a.type === RestrictedAllowType.RoomMembership)
|
||||
?.map(a => a.room_id)
|
||||
: undefined;
|
||||
|
||||
const guestAccess: GuestAccess = this.pullContentPropertyFromEvent<GuestAccess>(
|
||||
state.getStateEvents(EventType.RoomGuestAccess, ""),
|
||||
'guest_access',
|
||||
GuestAccess.Forbidden,
|
||||
);
|
||||
const history: HistoryVisibility = this.pullContentPropertyFromEvent(
|
||||
state.getStateEvents("m.room.history_visibility", ""),
|
||||
const history: HistoryVisibility = this.pullContentPropertyFromEvent<HistoryVisibility>(
|
||||
state.getStateEvents(EventType.RoomHistoryVisibility, ""),
|
||||
'history_visibility',
|
||||
HistoryVisibility.Shared,
|
||||
);
|
||||
|
||||
const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId);
|
||||
this.setState({ joinRule, guestAccess, history, encrypted });
|
||||
const hasAliases = await this.hasAliases();
|
||||
this.setState({ hasAliases });
|
||||
const restrictedRoomCapabilities = SpaceStore.instance.restrictedJoinRuleSupport;
|
||||
const roomSupportsRestricted = Array.isArray(restrictedRoomCapabilities?.support)
|
||||
&& restrictedRoomCapabilities.support.includes(room.getVersion());
|
||||
const preferredRestrictionVersion = roomSupportsRestricted ? undefined : restrictedRoomCapabilities?.preferred;
|
||||
this.setState({ joinRule, restrictedAllowRoomIds, guestAccess, history, encrypted,
|
||||
roomSupportsRestricted, preferredRestrictionVersion });
|
||||
|
||||
this.hasAliases().then(hasAliases => this.setState({ hasAliases }));
|
||||
}
|
||||
|
||||
private pullContentPropertyFromEvent<T>(event: MatrixEvent, key: string, defaultValue: T): T {
|
||||
if (!event || !event.getContent()) return defaultValue;
|
||||
return event.getContent()[key] || defaultValue;
|
||||
return event?.getContent()[key] || defaultValue;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -115,13 +120,13 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
}
|
||||
|
||||
private onStateEvent = (e: MatrixEvent) => {
|
||||
const refreshWhenTypes = [
|
||||
'm.room.join_rules',
|
||||
'm.room.guest_access',
|
||||
'm.room.history_visibility',
|
||||
'm.room.encryption',
|
||||
const refreshWhenTypes: EventType[] = [
|
||||
EventType.RoomJoinRules,
|
||||
EventType.RoomGuestAccess,
|
||||
EventType.RoomHistoryVisibility,
|
||||
EventType.RoomEncryption,
|
||||
];
|
||||
if (refreshWhenTypes.includes(e.getType())) this.forceUpdate();
|
||||
if (refreshWhenTypes.includes(e.getType() as EventType)) this.forceUpdate();
|
||||
};
|
||||
|
||||
private onEncryptionChange = () => {
|
||||
|
@ -133,9 +138,11 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
"may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>",
|
||||
{},
|
||||
{
|
||||
a: sub => <a href="https://element.io/help#encryption"
|
||||
rel="noreferrer noopener" target="_blank"
|
||||
>{sub}</a>,
|
||||
a: sub => <a
|
||||
href="https://element.io/help#encryption"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>{ sub }</a>,
|
||||
},
|
||||
),
|
||||
onFinished: (confirm) => {
|
||||
|
@ -147,7 +154,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
const beforeEncrypted = this.state.encrypted;
|
||||
this.setState({ encrypted: true });
|
||||
MatrixClientPeg.get().sendStateEvent(
|
||||
this.props.roomId, "m.room.encryption",
|
||||
this.props.roomId, EventType.RoomEncryption,
|
||||
{ algorithm: "m.megolm.v1.aes-sha2" },
|
||||
).catch((e) => {
|
||||
console.error(e);
|
||||
|
@ -157,89 +164,91 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
});
|
||||
};
|
||||
|
||||
private fixGuestAccess = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const joinRule = JoinRule.Invite;
|
||||
const guestAccess = GuestAccess.CanJoin;
|
||||
|
||||
private onJoinRuleChange = async (joinRule: JoinRule) => {
|
||||
const beforeJoinRule = this.state.joinRule;
|
||||
const beforeGuestAccess = this.state.guestAccess;
|
||||
this.setState({ joinRule, guestAccess });
|
||||
|
||||
let restrictedAllowRoomIds: string[];
|
||||
if (joinRule === JoinRule.Restricted) {
|
||||
const matrixClient = MatrixClientPeg.get();
|
||||
const roomId = this.props.roomId;
|
||||
const room = matrixClient.getRoom(roomId);
|
||||
|
||||
if (beforeJoinRule === JoinRule.Restricted || this.state.roomSupportsRestricted) {
|
||||
// Have the user pick which spaces to allow joins from
|
||||
restrictedAllowRoomIds = await this.editRestrictedRoomIds();
|
||||
if (!Array.isArray(restrictedAllowRoomIds)) return;
|
||||
} else if (this.state.preferredRestrictionVersion) {
|
||||
// Block this action on a room upgrade otherwise it'd make their room unjoinable
|
||||
const targetVersion = this.state.preferredRestrictionVersion;
|
||||
Modal.createTrackedDialog('Restricted join rule upgrade', '', RoomUpgradeWarningDialog, {
|
||||
roomId,
|
||||
targetVersion,
|
||||
description: _t("This upgrade will allow members of selected spaces " +
|
||||
"access to this room without an invite."),
|
||||
onFinished: (resp) => {
|
||||
if (!resp?.continue) return;
|
||||
upgradeRoom(room, targetVersion, resp.invite);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (beforeJoinRule === joinRule && !restrictedAllowRoomIds) return;
|
||||
|
||||
const content: IContent = {
|
||||
join_rule: joinRule,
|
||||
};
|
||||
|
||||
// pre-set the accepted spaces with the currently viewed one as per the microcopy
|
||||
if (joinRule === JoinRule.Restricted) {
|
||||
content.allow = restrictedAllowRoomIds.map(roomId => ({
|
||||
"type": RestrictedAllowType.RoomMembership,
|
||||
"room_id": roomId,
|
||||
}));
|
||||
}
|
||||
|
||||
this.setState({ joinRule, restrictedAllowRoomIds });
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
client.sendStateEvent(
|
||||
this.props.roomId,
|
||||
"m.room.join_rules",
|
||||
{ join_rule: joinRule },
|
||||
"",
|
||||
).catch((e) => {
|
||||
client.sendStateEvent(this.props.roomId, EventType.RoomJoinRules, content, "").catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({ joinRule: beforeJoinRule });
|
||||
});
|
||||
client.sendStateEvent(
|
||||
this.props.roomId,
|
||||
"m.room.guest_access",
|
||||
{ guest_access: guestAccess },
|
||||
"",
|
||||
).catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({ guestAccess: beforeGuestAccess });
|
||||
this.setState({
|
||||
joinRule: beforeJoinRule,
|
||||
restrictedAllowRoomIds: undefined,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
private onRoomAccessRadioToggle = (roomAccess: string) => {
|
||||
// join_rule
|
||||
// INVITE | PUBLIC
|
||||
// ----------------------+----------------
|
||||
// guest CAN_JOIN | inv_only | pub_with_guest
|
||||
// access ----------------------+----------------
|
||||
// FORBIDDEN | inv_only | pub_no_guest
|
||||
// ----------------------+----------------
|
||||
|
||||
// we always set guests can_join here as it makes no sense to have
|
||||
// an invite-only room that guests can't join. If you explicitly
|
||||
// invite them, you clearly want them to join, whether they're a
|
||||
// guest or not. In practice, guest_access should probably have
|
||||
// been implemented as part of the join_rules enum.
|
||||
let joinRule = JoinRule.Invite;
|
||||
let guestAccess = GuestAccess.CanJoin;
|
||||
|
||||
switch (roomAccess) {
|
||||
case "invite_only":
|
||||
// no change - use defaults above
|
||||
break;
|
||||
case "public_no_guests":
|
||||
joinRule = JoinRule.Public;
|
||||
guestAccess = GuestAccess.Forbidden;
|
||||
break;
|
||||
case "public_with_guests":
|
||||
joinRule = JoinRule.Public;
|
||||
guestAccess = GuestAccess.CanJoin;
|
||||
break;
|
||||
}
|
||||
|
||||
const beforeJoinRule = this.state.joinRule;
|
||||
const beforeGuestAccess = this.state.guestAccess;
|
||||
this.setState({ joinRule, guestAccess });
|
||||
private onRestrictedRoomIdsChange = (restrictedAllowRoomIds: string[]) => {
|
||||
const beforeRestrictedAllowRoomIds = this.state.restrictedAllowRoomIds;
|
||||
if (!arrayHasDiff(beforeRestrictedAllowRoomIds || [], restrictedAllowRoomIds)) return;
|
||||
this.setState({ restrictedAllowRoomIds });
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
client.sendStateEvent(
|
||||
this.props.roomId,
|
||||
"m.room.join_rules",
|
||||
{ join_rule: joinRule },
|
||||
"",
|
||||
).catch((e) => {
|
||||
client.sendStateEvent(this.props.roomId, EventType.RoomJoinRules, {
|
||||
join_rule: JoinRule.Restricted,
|
||||
allow: restrictedAllowRoomIds.map(roomId => ({
|
||||
"type": RestrictedAllowType.RoomMembership,
|
||||
"room_id": roomId,
|
||||
})),
|
||||
}, "").catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({ joinRule: beforeJoinRule });
|
||||
this.setState({ restrictedAllowRoomIds: beforeRestrictedAllowRoomIds });
|
||||
});
|
||||
client.sendStateEvent(
|
||||
this.props.roomId,
|
||||
"m.room.guest_access",
|
||||
{ guest_access: guestAccess },
|
||||
"",
|
||||
).catch((e) => {
|
||||
};
|
||||
|
||||
private onGuestAccessChange = (allowed: boolean) => {
|
||||
const guestAccess = allowed ? GuestAccess.CanJoin : GuestAccess.Forbidden;
|
||||
const beforeGuestAccess = this.state.guestAccess;
|
||||
if (beforeGuestAccess === guestAccess) return;
|
||||
|
||||
this.setState({ guestAccess });
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
client.sendStateEvent(this.props.roomId, EventType.RoomGuestAccess, {
|
||||
guest_access: guestAccess,
|
||||
}, "").catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({ guestAccess: beforeGuestAccess });
|
||||
});
|
||||
|
@ -247,8 +256,10 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
|
||||
private onHistoryRadioToggle = (history: HistoryVisibility) => {
|
||||
const beforeHistory = this.state.history;
|
||||
if (beforeHistory === history) return;
|
||||
|
||||
this.setState({ history: history });
|
||||
MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.history_visibility", {
|
||||
MatrixClientPeg.get().sendStateEvent(this.props.roomId, EventType.RoomHistoryVisibility, {
|
||||
history_visibility: history,
|
||||
}, "").catch((e) => {
|
||||
console.error(e);
|
||||
|
@ -268,74 +279,159 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
return Array.isArray(localAliases) && localAliases.length !== 0;
|
||||
} else {
|
||||
const room = cli.getRoom(this.props.roomId);
|
||||
const aliasEvents = room.currentState.getStateEvents("m.room.aliases") || [];
|
||||
const aliasEvents = room.currentState.getStateEvents(EventType.RoomAliases) || [];
|
||||
const hasAliases = !!aliasEvents.find((ev) => (ev.getContent().aliases || []).length > 0);
|
||||
return hasAliases;
|
||||
}
|
||||
}
|
||||
|
||||
private renderRoomAccess() {
|
||||
private editRestrictedRoomIds = async (): Promise<string[] | undefined> => {
|
||||
let selected = this.state.restrictedAllowRoomIds;
|
||||
if (!selected?.length && SpaceStore.instance.activeSpace) {
|
||||
selected = [SpaceStore.instance.activeSpace.roomId];
|
||||
}
|
||||
|
||||
const matrixClient = MatrixClientPeg.get();
|
||||
const { finished } = Modal.createTrackedDialog('Edit restricted', '', ManageRestrictedJoinRuleDialog, {
|
||||
matrixClient,
|
||||
room: matrixClient.getRoom(this.props.roomId),
|
||||
selected,
|
||||
}, "mx_ManageRestrictedJoinRuleDialog_wrapper");
|
||||
|
||||
const [restrictedAllowRoomIds] = await finished;
|
||||
return restrictedAllowRoomIds;
|
||||
};
|
||||
|
||||
private onEditRestrictedClick = async () => {
|
||||
const restrictedAllowRoomIds = await this.editRestrictedRoomIds();
|
||||
if (!Array.isArray(restrictedAllowRoomIds)) return;
|
||||
if (restrictedAllowRoomIds.length > 0) {
|
||||
this.onRestrictedRoomIdsChange(restrictedAllowRoomIds);
|
||||
} else {
|
||||
this.onJoinRuleChange(JoinRule.Invite);
|
||||
}
|
||||
};
|
||||
|
||||
private renderJoinRule() {
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = client.getRoom(this.props.roomId);
|
||||
const joinRule = this.state.joinRule;
|
||||
const guestAccess = this.state.guestAccess;
|
||||
|
||||
const canChangeAccess = room.currentState.mayClientSendStateEvent("m.room.join_rules", client)
|
||||
&& room.currentState.mayClientSendStateEvent("m.room.guest_access", client);
|
||||
|
||||
let guestWarning = null;
|
||||
if (joinRule !== 'public' && guestAccess === 'forbidden') {
|
||||
guestWarning = (
|
||||
<div className='mx_SecurityRoomSettingsTab_warning'>
|
||||
<img src={require("../../../../../../res/img/warning.svg")} width={15} height={15} />
|
||||
<span>
|
||||
{_t("Guests cannot join this room even if explicitly invited.")}
|
||||
<a href="" onClick={this.fixGuestAccess}>{_t("Click here to fix")}</a>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const canChangeJoinRule = room.currentState.mayClientSendStateEvent(EventType.RoomJoinRules, client);
|
||||
|
||||
let aliasWarning = null;
|
||||
if (joinRule === 'public' && !this.state.hasAliases) {
|
||||
if (joinRule === JoinRule.Public && !this.state.hasAliases) {
|
||||
aliasWarning = (
|
||||
<div className='mx_SecurityRoomSettingsTab_warning'>
|
||||
<img src={require("../../../../../../res/img/warning.svg")} width={15} height={15} />
|
||||
<span>
|
||||
{_t("To link to this room, please add an address.")}
|
||||
{ _t("To link to this room, please add an address.") }
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const radioDefinitions: IDefinition<JoinRule>[] = [{
|
||||
value: JoinRule.Invite,
|
||||
label: _t("Private (invite only)"),
|
||||
description: _t("Only invited people can join."),
|
||||
checked: this.state.joinRule === JoinRule.Invite
|
||||
|| (this.state.joinRule === JoinRule.Restricted && !this.state.restrictedAllowRoomIds?.length),
|
||||
}, {
|
||||
value: JoinRule.Public,
|
||||
label: _t("Public"),
|
||||
description: _t("Anyone can find and join."),
|
||||
}];
|
||||
|
||||
if (this.state.roomSupportsRestricted ||
|
||||
this.state.preferredRestrictionVersion ||
|
||||
joinRule === JoinRule.Restricted
|
||||
) {
|
||||
let upgradeRequiredPill;
|
||||
if (this.state.preferredRestrictionVersion) {
|
||||
upgradeRequiredPill = <span className="mx_SecurityRoomSettingsTab_upgradeRequired">
|
||||
{ _t("Upgrade required") }
|
||||
</span>;
|
||||
}
|
||||
|
||||
let description;
|
||||
if (joinRule === JoinRule.Restricted && this.state.restrictedAllowRoomIds?.length) {
|
||||
const shownSpaces = this.state.restrictedAllowRoomIds
|
||||
.map(roomId => client.getRoom(roomId))
|
||||
.filter(room => room?.isSpaceRoom())
|
||||
.slice(0, 4);
|
||||
|
||||
let moreText;
|
||||
if (shownSpaces.length < this.state.restrictedAllowRoomIds.length) {
|
||||
if (shownSpaces.length > 0) {
|
||||
moreText = _t("& %(count)s more", {
|
||||
count: this.state.restrictedAllowRoomIds.length - shownSpaces.length,
|
||||
});
|
||||
} else {
|
||||
moreText = _t("Currently, %(count)s spaces have access", {
|
||||
count: this.state.restrictedAllowRoomIds.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
description = <div>
|
||||
<span>
|
||||
{ _t("Anyone in a space can find and join. <a>Edit which spaces can access here.</a>", {}, {
|
||||
a: sub => <AccessibleButton
|
||||
disabled={!canChangeJoinRule}
|
||||
onClick={this.onEditRestrictedClick}
|
||||
kind="link"
|
||||
>
|
||||
{ sub }
|
||||
</AccessibleButton>,
|
||||
}) }
|
||||
</span>
|
||||
|
||||
<div className="mx_SecurityRoomSettingsTab_spacesWithAccess">
|
||||
<h4>{ _t("Spaces with access") }</h4>
|
||||
{ shownSpaces.map(room => {
|
||||
return <span key={room.roomId}>
|
||||
<RoomAvatar room={room} height={32} width={32} />
|
||||
{ room.name }
|
||||
</span>;
|
||||
}) }
|
||||
{ moreText && <span>{ moreText }</span> }
|
||||
</div>
|
||||
</div>;
|
||||
} else if (SpaceStore.instance.activeSpace) {
|
||||
description = _t("Anyone in %(spaceName)s can find and join. You can select other spaces too.", {
|
||||
spaceName: SpaceStore.instance.activeSpace.name,
|
||||
});
|
||||
} else {
|
||||
description = _t("Anyone in a space can find and join. You can select multiple spaces.");
|
||||
}
|
||||
|
||||
radioDefinitions.splice(1, 0, {
|
||||
value: JoinRule.Restricted,
|
||||
label: <>
|
||||
{ _t("Space members") }
|
||||
{ upgradeRequiredPill }
|
||||
</>,
|
||||
description,
|
||||
// if there are 0 allowed spaces then render it as invite only instead
|
||||
checked: this.state.joinRule === JoinRule.Restricted && !!this.state.restrictedAllowRoomIds?.length,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{guestWarning}
|
||||
{aliasWarning}
|
||||
<div className="mx_SecurityRoomSettingsTab_joinRule">
|
||||
<div className="mx_SettingsTab_subsectionText">
|
||||
<span>{ _t("Decide who can join %(roomName)s.", {
|
||||
roomName: client.getRoom(this.props.roomId)?.name,
|
||||
}) }</span>
|
||||
</div>
|
||||
{ aliasWarning }
|
||||
<StyledRadioGroup
|
||||
name="roomVis"
|
||||
name="joinRule"
|
||||
value={joinRule}
|
||||
onChange={this.onRoomAccessRadioToggle}
|
||||
definitions={[
|
||||
{
|
||||
value: "invite_only",
|
||||
disabled: !canChangeAccess,
|
||||
label: _t('Only people who have been invited'),
|
||||
checked: joinRule !== "public",
|
||||
},
|
||||
{
|
||||
value: "public_no_guests",
|
||||
disabled: !canChangeAccess,
|
||||
label: _t('Anyone who knows the room\'s link, apart from guests'),
|
||||
checked: joinRule === "public" && guestAccess !== "can_join",
|
||||
},
|
||||
{
|
||||
value: "public_with_guests",
|
||||
disabled: !canChangeAccess,
|
||||
label: _t("Anyone who knows the room's link, including guests"),
|
||||
checked: joinRule === "public" && guestAccess === "can_join",
|
||||
},
|
||||
]}
|
||||
onChange={this.onJoinRuleChange}
|
||||
definitions={radioDefinitions}
|
||||
disabled={!canChangeJoinRule}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -345,7 +441,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
const client = MatrixClientPeg.get();
|
||||
const history = this.state.history;
|
||||
const state = client.getRoom(this.props.roomId).currentState;
|
||||
const canChangeHistory = state.mayClientSendStateEvent('m.room.history_visibility', client);
|
||||
const canChangeHistory = state.mayClientSendStateEvent(EventType.RoomHistoryVisibility, client);
|
||||
|
||||
const options = [
|
||||
{
|
||||
|
@ -373,8 +469,8 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
return (
|
||||
<div>
|
||||
<div>
|
||||
{_t('Changes to who can read history will only apply to future messages in this room. ' +
|
||||
'The visibility of existing history will be unchanged.')}
|
||||
{ _t('Changes to who can read history will only apply to future messages in this room. ' +
|
||||
'The visibility of existing history will be unchanged.') }
|
||||
</div>
|
||||
<StyledRadioGroup
|
||||
name="historyVis"
|
||||
|
@ -387,11 +483,35 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
);
|
||||
}
|
||||
|
||||
private toggleAdvancedSection = () => {
|
||||
this.setState({ showAdvancedSection: !this.state.showAdvancedSection });
|
||||
};
|
||||
|
||||
private renderAdvanced() {
|
||||
const client = MatrixClientPeg.get();
|
||||
const guestAccess = this.state.guestAccess;
|
||||
const state = client.getRoom(this.props.roomId).currentState;
|
||||
const canSetGuestAccess = state.mayClientSendStateEvent(EventType.RoomGuestAccess, client);
|
||||
|
||||
return <div className="mx_SettingsTab_section">
|
||||
<LabelledToggleSwitch
|
||||
value={guestAccess === GuestAccess.CanJoin}
|
||||
onChange={this.onGuestAccessChange}
|
||||
disabled={!canSetGuestAccess}
|
||||
label={_t("Enable guest access")}
|
||||
/>
|
||||
<p>
|
||||
{ _t("People with supported clients will be able to join " +
|
||||
"the room without having a registered account.") }
|
||||
</p>
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = client.getRoom(this.props.roomId);
|
||||
const isEncrypted = this.state.encrypted;
|
||||
const hasEncryptionPermission = room.currentState.mayClientSendStateEvent("m.room.encryption", client);
|
||||
const hasEncryptionPermission = room.currentState.mayClientSendStateEvent(EventType.RoomEncryption, client);
|
||||
const canEnableEncryption = !isEncrypted && hasEncryptionPermission;
|
||||
|
||||
let encryptionSettings = null;
|
||||
|
@ -405,9 +525,9 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
}
|
||||
|
||||
let historySection = (<>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Who can read history?")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Who can read history?") }</span>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
{this.renderHistory()}
|
||||
{ this.renderHistory() }
|
||||
</div>
|
||||
</>);
|
||||
if (!SettingsStore.getValue(UIFeature.RoomHistorySettings)) {
|
||||
|
@ -416,27 +536,39 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_SecurityRoomSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Security & Privacy")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Security & Privacy") }</div>
|
||||
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Encryption")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Encryption") }</span>
|
||||
<div className='mx_SettingsTab_section mx_SecurityRoomSettingsTab_encryptionSection'>
|
||||
<div>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
<span>{_t("Once enabled, encryption cannot be disabled.")}</span>
|
||||
<span>{ _t("Once enabled, encryption cannot be disabled.") }</span>
|
||||
</div>
|
||||
<LabelledToggleSwitch value={isEncrypted} onChange={this.onEncryptionChange}
|
||||
label={_t("Encrypted")} disabled={!canEnableEncryption}
|
||||
<LabelledToggleSwitch
|
||||
value={isEncrypted}
|
||||
onChange={this.onEncryptionChange}
|
||||
label={_t("Encrypted")}
|
||||
disabled={!canEnableEncryption}
|
||||
/>
|
||||
</div>
|
||||
{encryptionSettings}
|
||||
{ encryptionSettings }
|
||||
</div>
|
||||
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Who can access this room?")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Access") }</span>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
{this.renderRoomAccess()}
|
||||
{ this.renderJoinRule() }
|
||||
</div>
|
||||
|
||||
{historySection}
|
||||
<AccessibleButton
|
||||
onClick={this.toggleAdvancedSection}
|
||||
kind="link"
|
||||
className="mx_SettingsTab_showAdvanced"
|
||||
>
|
||||
{ this.state.showAdvancedSection ? _t("Hide advanced") : _t("Show advanced") }
|
||||
</AccessibleButton>
|
||||
{ this.state.showAdvancedSection && this.renderAdvanced() }
|
||||
|
||||
{ historySection }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ import StyledRadioGroup from "../../../elements/StyledRadioGroup";
|
|||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import { UIFeature } from "../../../../../settings/UIFeature";
|
||||
import { Layout } from "../../../../../settings/Layout";
|
||||
import classNames from 'classnames';
|
||||
import StyledRadioButton from '../../../elements/StyledRadioButton';
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
import { compare } from "../../../../../utils/strings";
|
||||
|
||||
|
@ -241,6 +243,19 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
this.setState({ customThemeUrl: e.target.value });
|
||||
};
|
||||
|
||||
private onLayoutChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
let layout;
|
||||
switch (e.target.value) {
|
||||
case "irc": layout = Layout.IRC; break;
|
||||
case "group": layout = Layout.Group; break;
|
||||
case "bubble": layout = Layout.Bubble; break;
|
||||
}
|
||||
|
||||
this.setState({ layout: layout });
|
||||
|
||||
SettingsStore.setValue("layout", null, SettingLevel.DEVICE, layout);
|
||||
};
|
||||
|
||||
private onIRCLayoutChange = (enabled: boolean) => {
|
||||
if (enabled) {
|
||||
this.setState({ layout: Layout.IRC });
|
||||
|
@ -260,7 +275,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
checked={this.state.useSystemTheme}
|
||||
onChange={(e) => this.onUseSystemThemeChanged(e.target.checked)}
|
||||
>
|
||||
{SettingsStore.getDisplayName("use_system_theme")}
|
||||
{ SettingsStore.getDisplayName("use_system_theme") }
|
||||
</StyledCheckbox>
|
||||
</div>;
|
||||
}
|
||||
|
@ -270,9 +285,9 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
let messageElement = null;
|
||||
if (this.state.customThemeMessage.text) {
|
||||
if (this.state.customThemeMessage.isError) {
|
||||
messageElement = <div className='text-error'>{this.state.customThemeMessage.text}</div>;
|
||||
messageElement = <div className='text-error'>{ this.state.customThemeMessage.text }</div>;
|
||||
} else {
|
||||
messageElement = <div className='text-success'>{this.state.customThemeMessage.text}</div>;
|
||||
messageElement = <div className='text-success'>{ this.state.customThemeMessage.text }</div>;
|
||||
}
|
||||
}
|
||||
customThemeForm = (
|
||||
|
@ -288,10 +303,13 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
/>
|
||||
<AccessibleButton
|
||||
onClick={this.onAddCustomTheme}
|
||||
type="submit" kind="primary_sm"
|
||||
type="submit"
|
||||
kind="primary_sm"
|
||||
disabled={!this.state.customThemeUrl.trim()}
|
||||
>{_t("Add theme")}</AccessibleButton>
|
||||
{messageElement}
|
||||
>
|
||||
{ _t("Add theme") }
|
||||
</AccessibleButton>
|
||||
{ messageElement }
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
@ -306,8 +324,8 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
const orderedThemes = [...builtInThemes, ...customThemes];
|
||||
return (
|
||||
<div className="mx_SettingsTab_section mx_AppearanceUserSettingsTab_themeSection">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
|
||||
{systemThemeSection}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Theme") }</span>
|
||||
{ systemThemeSection }
|
||||
<div className="mx_ThemeSelectors">
|
||||
<StyledRadioGroup
|
||||
name="theme"
|
||||
|
@ -322,7 +340,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
outlined
|
||||
/>
|
||||
</div>
|
||||
{customThemeForm}
|
||||
{ customThemeForm }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -330,7 +348,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
private renderFontSection() {
|
||||
return <div className="mx_SettingsTab_section mx_AppearanceUserSettingsTab_fontScaling">
|
||||
|
||||
<span className="mx_SettingsTab_subheading">{_t("Font size")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Font size") }</span>
|
||||
<EventTilePreview
|
||||
className="mx_AppearanceUserSettingsTab_fontSlider_preview"
|
||||
message={this.MESSAGE_PREVIEW_TEXT}
|
||||
|
@ -373,6 +391,75 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
</div>;
|
||||
}
|
||||
|
||||
private renderLayoutSection = () => {
|
||||
return <div className="mx_SettingsTab_section mx_AppearanceUserSettingsTab_Layout">
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Message layout") }</span>
|
||||
|
||||
<div className="mx_AppearanceUserSettingsTab_Layout_RadioButtons">
|
||||
<label className={classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", {
|
||||
mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout == Layout.IRC,
|
||||
})}>
|
||||
<EventTilePreview
|
||||
className="mx_AppearanceUserSettingsTab_Layout_RadioButton_preview"
|
||||
message={this.MESSAGE_PREVIEW_TEXT}
|
||||
layout={Layout.IRC}
|
||||
userId={this.state.userId}
|
||||
displayName={this.state.displayName}
|
||||
avatarUrl={this.state.avatarUrl}
|
||||
/>
|
||||
<StyledRadioButton
|
||||
name="layout"
|
||||
value="irc"
|
||||
checked={this.state.layout === Layout.IRC}
|
||||
onChange={this.onLayoutChange}
|
||||
>
|
||||
{ _t("IRC") }
|
||||
</StyledRadioButton>
|
||||
</label>
|
||||
<label className={classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", {
|
||||
mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout == Layout.Group,
|
||||
})}>
|
||||
<EventTilePreview
|
||||
className="mx_AppearanceUserSettingsTab_Layout_RadioButton_preview"
|
||||
message={this.MESSAGE_PREVIEW_TEXT}
|
||||
layout={Layout.Group}
|
||||
userId={this.state.userId}
|
||||
displayName={this.state.displayName}
|
||||
avatarUrl={this.state.avatarUrl}
|
||||
/>
|
||||
<StyledRadioButton
|
||||
name="layout"
|
||||
value="group"
|
||||
checked={this.state.layout == Layout.Group}
|
||||
onChange={this.onLayoutChange}
|
||||
>
|
||||
{ _t("Modern") }
|
||||
</StyledRadioButton>
|
||||
</label>
|
||||
<label className={classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", {
|
||||
mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout === Layout.Bubble,
|
||||
})}>
|
||||
<EventTilePreview
|
||||
className="mx_AppearanceUserSettingsTab_Layout_RadioButton_preview"
|
||||
message={this.MESSAGE_PREVIEW_TEXT}
|
||||
layout={Layout.Bubble}
|
||||
userId={this.state.userId}
|
||||
displayName={this.state.displayName}
|
||||
avatarUrl={this.state.avatarUrl}
|
||||
/>
|
||||
<StyledRadioButton
|
||||
name="layout"
|
||||
value="bubble"
|
||||
checked={this.state.layout == Layout.Bubble}
|
||||
onChange={this.onLayoutChange}
|
||||
>
|
||||
{ _t("Message bubbles") }
|
||||
</StyledRadioButton>
|
||||
</label>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
private renderAdvancedSection() {
|
||||
if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null;
|
||||
|
||||
|
@ -381,7 +468,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
className="mx_AppearanceUserSettingsTab_AdvancedToggle"
|
||||
onClick={() => this.setState({ showAdvanced: !this.state.showAdvanced })}
|
||||
>
|
||||
{this.state.showAdvanced ? _t("Hide advanced") : _t("Show advanced")}
|
||||
{ this.state.showAdvanced ? _t("Hide advanced") : _t("Show advanced") }
|
||||
</div>;
|
||||
|
||||
let advanced: React.ReactNode;
|
||||
|
@ -396,14 +483,17 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
name="useCompactLayout"
|
||||
level={SettingLevel.DEVICE}
|
||||
useCheckbox={true}
|
||||
disabled={this.state.layout == Layout.IRC}
|
||||
disabled={this.state.layout !== Layout.Group}
|
||||
/>
|
||||
<StyledCheckbox
|
||||
checked={this.state.layout == Layout.IRC}
|
||||
onChange={(ev) => this.onIRCLayoutChange(ev.target.checked)}
|
||||
>
|
||||
{_t("Enable experimental, compact IRC style layout")}
|
||||
</StyledCheckbox>
|
||||
|
||||
{ !SettingsStore.getValue("feature_new_layout_switcher") ?
|
||||
<StyledCheckbox
|
||||
checked={this.state.layout == Layout.IRC}
|
||||
onChange={(ev) => this.onIRCLayoutChange(ev.target.checked)}
|
||||
>
|
||||
{ _t("Enable experimental, compact IRC style layout") }
|
||||
</StyledCheckbox> : null
|
||||
}
|
||||
|
||||
<SettingsFlag
|
||||
name="useSystemFont"
|
||||
|
@ -429,8 +519,8 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
</>;
|
||||
}
|
||||
return <div className="mx_SettingsTab_section mx_AppearanceUserSettingsTab_Advanced">
|
||||
{toggle}
|
||||
{advanced}
|
||||
{ toggle }
|
||||
{ advanced }
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
@ -439,13 +529,14 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_AppearanceUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Customise your appearance")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Customise your appearance") }</div>
|
||||
<div className="mx_SettingsTab_SubHeading">
|
||||
{_t("Appearance Settings only affect this %(brand)s session.", { brand })}
|
||||
{ _t("Appearance Settings only affect this %(brand)s session.", { brand }) }
|
||||
</div>
|
||||
{this.renderThemeSection()}
|
||||
{this.renderFontSection()}
|
||||
{this.renderAdvancedSection()}
|
||||
{ this.renderThemeSection() }
|
||||
{ SettingsStore.getValue("feature_new_layout_switcher") ? this.renderLayoutSection() : null }
|
||||
{ this.renderFontSection() }
|
||||
{ this.renderAdvancedSection() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ export default class FlairUserSettingsTab extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<span className="mx_SettingsTab_heading">{_t("Flair")}</span>
|
||||
<span className="mx_SettingsTab_heading">{ _t("Flair") }</span>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<GroupUserSettings />
|
||||
</div>
|
||||
|
|
|
@ -289,11 +289,11 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
onMsisdnsChange={this._onMsisdnsChange}
|
||||
/>;
|
||||
threepidSection = <div>
|
||||
<span className="mx_SettingsTab_subheading">{_t("Email addresses")}</span>
|
||||
{emails}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Email addresses") }</span>
|
||||
{ emails }
|
||||
|
||||
<span className="mx_SettingsTab_subheading">{_t("Phone numbers")}</span>
|
||||
{msisdns}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Phone numbers") }</span>
|
||||
{ msisdns }
|
||||
</div>;
|
||||
} else if (this.state.serverSupportsSeparateAddAndBind === null) {
|
||||
threepidSection = <Spinner />;
|
||||
|
@ -308,12 +308,12 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
|
||||
return (
|
||||
<div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_accountSection">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Account")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Account") }</span>
|
||||
<p className="mx_SettingsTab_subsectionText">
|
||||
{passwordChangeText}
|
||||
{ passwordChangeText }
|
||||
</p>
|
||||
{passwordChangeForm}
|
||||
{threepidSection}
|
||||
{ passwordChangeForm }
|
||||
{ threepidSection }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -322,7 +322,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
// TODO: Convert to new-styled Field
|
||||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Language and region")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Language and region") }</span>
|
||||
<LanguageDropdown
|
||||
className="mx_GeneralUserSettingsTab_languageInput"
|
||||
onOptionChange={this._onLanguageChange}
|
||||
|
@ -335,7 +335,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
_renderSpellCheckSection() {
|
||||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Spell check dictionaries")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Spell check dictionaries") }</span>
|
||||
<SpellCheckSettings
|
||||
languages={this.state.spellCheckLanguages}
|
||||
onLanguagesChange={this._onSpellCheckLanguagesChange}
|
||||
|
@ -350,11 +350,11 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
if (this.state.requiredPolicyInfo.hasTerms) {
|
||||
const InlineTermsAgreement = sdk.getComponent("views.terms.InlineTermsAgreement");
|
||||
const intro = <span className="mx_SettingsTab_subsectionText">
|
||||
{_t(
|
||||
{ _t(
|
||||
"Agree to the identity server (%(serverName)s) Terms of Service to " +
|
||||
"allow yourself to be discoverable by email address or phone number.",
|
||||
{ serverName: this.state.idServerName },
|
||||
)}
|
||||
) }
|
||||
</span>;
|
||||
return (
|
||||
<div>
|
||||
|
@ -377,16 +377,16 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
const msisdns = this.state.loading3pids ? <Spinner /> : <PhoneNumbers msisdns={this.state.msisdns} />;
|
||||
|
||||
const threepidSection = this.state.haveIdServer ? <div className='mx_GeneralUserSettingsTab_discovery'>
|
||||
<span className="mx_SettingsTab_subheading">{_t("Email addresses")}</span>
|
||||
{emails}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Email addresses") }</span>
|
||||
{ emails }
|
||||
|
||||
<span className="mx_SettingsTab_subheading">{_t("Phone numbers")}</span>
|
||||
{msisdns}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Phone numbers") }</span>
|
||||
{ msisdns }
|
||||
</div> : null;
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
{threepidSection}
|
||||
{ threepidSection }
|
||||
{ /* has its own heading as it includes the current identity server */ }
|
||||
<SetIdServer />
|
||||
</div>
|
||||
|
@ -397,12 +397,12 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
// TODO: Improve warning text for account deactivation
|
||||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Account management")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Account management") }</span>
|
||||
<span className="mx_SettingsTab_subsectionText">
|
||||
{_t("Deactivating your account is a permanent action - be careful!")}
|
||||
{ _t("Deactivating your account is a permanent action - be careful!") }
|
||||
</span>
|
||||
<AccessibleButton onClick={this._onDeactivateClicked} kind="danger">
|
||||
{_t("Deactivate Account")}
|
||||
{ _t("Deactivate Account") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
|
@ -426,36 +426,40 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
const supportsMultiLanguageSpellCheck = plaf.supportsMultiLanguageSpellCheck();
|
||||
|
||||
const discoWarning = this.state.requiredPolicyInfo.hasTerms
|
||||
? <img className='mx_GeneralUserSettingsTab_warningIcon'
|
||||
? <img
|
||||
className='mx_GeneralUserSettingsTab_warningIcon'
|
||||
src={require("../../../../../../res/img/feather-customised/warning-triangle.svg")}
|
||||
width="18" height="18" alt={_t("Warning")} />
|
||||
width="18"
|
||||
height="18"
|
||||
alt={_t("Warning")}
|
||||
/>
|
||||
: null;
|
||||
|
||||
let accountManagementSection;
|
||||
if (SettingsStore.getValue(UIFeature.Deactivate)) {
|
||||
accountManagementSection = <>
|
||||
<div className="mx_SettingsTab_heading">{_t("Deactivate account")}</div>
|
||||
{this._renderManagementSection()}
|
||||
<div className="mx_SettingsTab_heading">{ _t("Deactivate account") }</div>
|
||||
{ this._renderManagementSection() }
|
||||
</>;
|
||||
}
|
||||
|
||||
let discoverySection;
|
||||
if (SettingsStore.getValue(UIFeature.IdentityServer)) {
|
||||
discoverySection = <>
|
||||
<div className="mx_SettingsTab_heading">{discoWarning} {_t("Discovery")}</div>
|
||||
{this._renderDiscoverySection()}
|
||||
<div className="mx_SettingsTab_heading">{ discoWarning } { _t("Discovery") }</div>
|
||||
{ this._renderDiscoverySection() }
|
||||
</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
||||
{this._renderProfileSection()}
|
||||
{this._renderAccountSection()}
|
||||
{this._renderLanguageSection()}
|
||||
{supportsMultiLanguageSpellCheck ? this._renderSpellCheckSection() : null}
|
||||
<div className="mx_SettingsTab_heading">{ _t("General") }</div>
|
||||
{ this._renderProfileSection() }
|
||||
{ this._renderAccountSection() }
|
||||
{ this._renderLanguageSection() }
|
||||
{ supportsMultiLanguageSpellCheck ? this._renderSpellCheckSection() : null }
|
||||
{ discoverySection }
|
||||
{this._renderIntegrationManagerSection() /* Has its own title */}
|
||||
{ this._renderIntegrationManagerSection() /* Has its own title */ }
|
||||
{ accountManagementSection }
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -112,15 +112,15 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
const legalLinks = [];
|
||||
for (const tocEntry of SdkConfig.get().terms_and_conditions_links) {
|
||||
legalLinks.push(<div key={tocEntry.url}>
|
||||
<a href={tocEntry.url} rel="noreferrer noopener" target="_blank">{tocEntry.text}</a>
|
||||
<a href={tocEntry.url} rel="noreferrer noopener" target="_blank">{ tocEntry.text }</a>
|
||||
</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mx_SettingsTab_section mx_HelpUserSettingsTab_versions'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Legal")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Legal") }</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{legalLinks}
|
||||
{ legalLinks }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -131,31 +131,42 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
// Also, is ugly but necessary.
|
||||
return (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Credits")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Credits") }</span>
|
||||
<ul>
|
||||
<li>
|
||||
The <a href="themes/element/img/backgrounds/lake.jpg" rel="noreferrer noopener"
|
||||
target="_blank">default cover photo</a> is ©
|
||||
<a href="https://www.flickr.com/golan" rel="noreferrer noopener"
|
||||
target="_blank">Jesús Roncero</a> used under the terms of
|
||||
<a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="noreferrer noopener"
|
||||
target="_blank">CC-BY-SA 4.0</a>.
|
||||
The <a href="themes/element/img/backgrounds/lake.jpg" rel="noreferrer noopener" target="_blank">
|
||||
default cover photo
|
||||
</a> is ©
|
||||
<a href="https://www.flickr.com/golan" rel="noreferrer noopener" target="_blank">
|
||||
Jesús Roncero
|
||||
</a> used under the terms of
|
||||
<a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="noreferrer noopener" target="_blank">
|
||||
CC-BY-SA 4.0
|
||||
</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="https://github.com/matrix-org/twemoji-colr" rel="noreferrer noopener"
|
||||
target="_blank">twemoji-colr</a> font is ©
|
||||
<a href="https://mozilla.org" rel="noreferrer noopener"
|
||||
target="_blank">Mozilla Foundation</a> used under the terms of
|
||||
<a href="http://www.apache.org/licenses/LICENSE-2.0" rel="noreferrer noopener"
|
||||
target="_blank">Apache 2.0</a>.
|
||||
The <a
|
||||
href="https://github.com/matrix-org/twemoji-colr"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
twemoji-colr
|
||||
</a> font is ©
|
||||
<a href="https://mozilla.org" rel="noreferrer noopener" target="_blank">
|
||||
Mozilla Foundation
|
||||
</a> used under the terms of
|
||||
<a href="http://www.apache.org/licenses/LICENSE-2.0" rel="noreferrer noopener" target="_blank">Apache 2.0</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="https://twemoji.twitter.com/" rel="noreferrer noopener"
|
||||
target="_blank">Twemoji</a> emoji art is ©
|
||||
<a href="https://twemoji.twitter.com/" rel="noreferrer noopener"
|
||||
target="_blank">Twitter, Inc and other contributors</a> used under the terms of
|
||||
<a href="https://creativecommons.org/licenses/by/4.0/" rel="noreferrer noopener"
|
||||
target="_blank">CC-BY 4.0</a>.
|
||||
The <a href="https://twemoji.twitter.com/" rel="noreferrer noopener" target="_blank">
|
||||
Twemoji
|
||||
</a> emoji art is ©
|
||||
<a href="https://twemoji.twitter.com/" rel="noreferrer noopener" target="_blank">
|
||||
Twitter, Inc and other contributors
|
||||
</a> used under the terms of
|
||||
<a href="https://creativecommons.org/licenses/by/4.0/" rel="noreferrer noopener" target="_blank">
|
||||
CC-BY 4.0
|
||||
</a>.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -189,14 +200,14 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
{sub}
|
||||
{ sub }
|
||||
</a>,
|
||||
},
|
||||
);
|
||||
if (SdkConfig.get().welcomeUserId && getCurrentLanguage().startsWith('en')) {
|
||||
faqText = (
|
||||
<div>
|
||||
{_t(
|
||||
{ _t(
|
||||
'For help with using %(brand)s, click <a>here</a> or start a chat with our ' +
|
||||
'bot using the button below.',
|
||||
{
|
||||
|
@ -208,13 +219,13 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
rel='noreferrer noopener'
|
||||
target='_blank'
|
||||
>
|
||||
{sub}
|
||||
{ sub }
|
||||
</a>,
|
||||
},
|
||||
)}
|
||||
) }
|
||||
<div>
|
||||
<AccessibleButton onClick={this.onStartBotChat} kind='primary'>
|
||||
{_t("Chat with %(brand)s Bot", { brand })}
|
||||
{ _t("Chat with %(brand)s Bot", { brand }) }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -235,29 +246,30 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
if (SdkConfig.get().bug_report_endpoint_url) {
|
||||
bugReportingSection = (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className='mx_SettingsTab_subheading'>{_t('Bug reporting')}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t('Bug reporting') }</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t(
|
||||
{ _t(
|
||||
"If you've submitted a bug via GitHub, debug logs can help " +
|
||||
"us track down the problem. Debug logs contain application " +
|
||||
"usage data including your username, the IDs or aliases of " +
|
||||
"the rooms or groups you have visited and the usernames of " +
|
||||
"other users. They do not contain messages.",
|
||||
)}
|
||||
) }
|
||||
<div className='mx_HelpUserSettingsTab_debugButton'>
|
||||
<AccessibleButton onClick={this.onBugReport} kind='primary'>
|
||||
{_t("Submit debug logs")}
|
||||
{ _t("Submit debug logs") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
{_t(
|
||||
{ _t(
|
||||
"To report a Matrix-related security issue, please read the Matrix.org " +
|
||||
"<a>Security Disclosure Policy</a>.", {},
|
||||
{
|
||||
a: sub => <a href="https://matrix.org/security-disclosure-policy/"
|
||||
rel="noreferrer noopener" target="_blank"
|
||||
>{sub}</a>,
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>{ sub }</a>,
|
||||
},
|
||||
)}
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -265,39 +277,39 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_HelpUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Help & About")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Help & About") }</div>
|
||||
{ bugReportingSection }
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("FAQ")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("FAQ") }</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{faqText}
|
||||
{ faqText }
|
||||
</div>
|
||||
<AccessibleButton kind="primary" onClick={KeyboardShortcuts.toggleDialog}>
|
||||
{ _t("Keyboard Shortcuts") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<div className='mx_SettingsTab_section mx_HelpUserSettingsTab_versions'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Versions")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Versions") }</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t("%(brand)s version:", { brand })} {appVersion}<br />
|
||||
{_t("olm version:")} {olmVersion}<br />
|
||||
{updateButton}
|
||||
{ _t("%(brand)s version:", { brand }) } { appVersion }<br />
|
||||
{ _t("olm version:") } { olmVersion }<br />
|
||||
{ updateButton }
|
||||
</div>
|
||||
</div>
|
||||
{this.renderLegal()}
|
||||
{this.renderCredits()}
|
||||
{ this.renderLegal() }
|
||||
{ this.renderCredits() }
|
||||
<div className='mx_SettingsTab_section mx_HelpUserSettingsTab_versions'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Advanced")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Advanced") }</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t("Homeserver is")} <code>{MatrixClientPeg.get().getHomeserverUrl()}</code><br />
|
||||
{_t("Identity server is")} <code>{MatrixClientPeg.get().getIdentityServerUrl()}</code><br />
|
||||
{ _t("Homeserver is") } <code>{ MatrixClientPeg.get().getHomeserverUrl() }</code><br />
|
||||
{ _t("Identity server is") } <code>{ MatrixClientPeg.get().getIdentityServerUrl() }</code><br />
|
||||
<br />
|
||||
<details>
|
||||
<summary>{_t("Access Token")}</summary><br />
|
||||
<b>{_t("Your access token gives full access to your account."
|
||||
+ " Do not share it with anyone." )}</b>
|
||||
<summary>{ _t("Access Token") }</summary><br />
|
||||
<b>{ _t("Your access token gives full access to your account."
|
||||
+ " Do not share it with anyone." ) }</b>
|
||||
<div className="mx_HelpUserSettingsTab_accessToken">
|
||||
<code>{MatrixClientPeg.get().getAccessToken()}</code>
|
||||
<code>{ MatrixClientPeg.get().getAccessToken() }</code>
|
||||
<AccessibleTooltipButton
|
||||
title={_t("Copy")}
|
||||
onClick={this.onAccessTokenCopyClick}
|
||||
|
@ -307,7 +319,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
</details><br />
|
||||
<div className='mx_HelpUserSettingsTab_debugButton'>
|
||||
<AccessibleButton onClick={this.onClearCacheAndReload} kind='danger'>
|
||||
{_t("Clear cache and reload")}
|
||||
{ _t("Clear cache and reload") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -69,7 +69,7 @@ export default class LabsUserSettingsTab extends React.Component {
|
|||
const flags = labs.map(f => <LabsSettingToggle featureId={f} key={f} />);
|
||||
|
||||
labsSection = <div className="mx_SettingsTab_section">
|
||||
{flags}
|
||||
{ flags }
|
||||
<SettingsFlag name="enableWidgetScreenshots" level={SettingLevel.ACCOUNT} />
|
||||
<SettingsFlag name="showHiddenEventsInTimeline" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="lowBandwidth" level={SettingLevel.DEVICE} />
|
||||
|
@ -79,15 +79,18 @@ export default class LabsUserSettingsTab extends React.Component {
|
|||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_LabsUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Labs")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Labs") }</div>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{
|
||||
_t('Feeling experimental? Labs are the best way to get things early, ' +
|
||||
'test out new features and help shape them before they actually launch. ' +
|
||||
'<a>Learn more</a>.', {}, {
|
||||
'a': (sub) => {
|
||||
return <a href="https://github.com/vector-im/element-web/blob/develop/docs/labs.md"
|
||||
rel='noreferrer noopener' target='_blank'>{sub}</a>;
|
||||
return <a
|
||||
href="https://github.com/vector-im/element-web/blob/develop/docs/labs.md"
|
||||
rel='noreferrer noopener'
|
||||
target='_blank'
|
||||
>{ sub }</a>;
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -140,23 +140,23 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
const name = room ? room.name : list.roomId;
|
||||
|
||||
const renderRules = (rules: ListRule[]) => {
|
||||
if (rules.length === 0) return <i>{_t("None")}</i>;
|
||||
if (rules.length === 0) return <i>{ _t("None") }</i>;
|
||||
|
||||
const tiles = [];
|
||||
for (const rule of rules) {
|
||||
tiles.push(<li key={rule.kind + rule.entity}><code>{rule.entity}</code></li>);
|
||||
tiles.push(<li key={rule.kind + rule.entity}><code>{ rule.entity }</code></li>);
|
||||
}
|
||||
return <ul>{tiles}</ul>;
|
||||
return <ul>{ tiles }</ul>;
|
||||
};
|
||||
|
||||
Modal.createTrackedDialog('View Mjolnir list rules', '', QuestionDialog, {
|
||||
title: _t("Ban list rules - %(roomName)s", { roomName: name }),
|
||||
description: (
|
||||
<div>
|
||||
<h3>{_t("Server rules")}</h3>
|
||||
{renderRules(list.serverRules)}
|
||||
<h3>{_t("User rules")}</h3>
|
||||
{renderRules(list.userRules)}
|
||||
<h3>{ _t("Server rules") }</h3>
|
||||
{ renderRules(list.serverRules) }
|
||||
<h3>{ _t("User rules") }</h3>
|
||||
{ renderRules(list.userRules) }
|
||||
</div>
|
||||
),
|
||||
button: _t("Close"),
|
||||
|
@ -167,7 +167,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
private renderPersonalBanListRules() {
|
||||
const list = Mjolnir.sharedInstance().getPersonalList();
|
||||
const rules = list ? [...list.userRules, ...list.serverRules] : [];
|
||||
if (!list || rules.length <= 0) return <i>{_t("You have not ignored anyone.")}</i>;
|
||||
if (!list || rules.length <= 0) return <i>{ _t("You have not ignored anyone.") }</i>;
|
||||
|
||||
const tiles = [];
|
||||
for (const rule of rules) {
|
||||
|
@ -178,17 +178,17 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
onClick={() => this.removePersonalRule(rule)}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
{_t("Remove")}
|
||||
{ _t("Remove") }
|
||||
</AccessibleButton>
|
||||
<code>{rule.entity}</code>
|
||||
<code>{ rule.entity }</code>
|
||||
</li>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{_t("You are currently ignoring:")}</p>
|
||||
<ul>{tiles}</ul>
|
||||
<p>{ _t("You are currently ignoring:") }</p>
|
||||
<ul>{ tiles }</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -198,12 +198,12 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
const lists = Mjolnir.sharedInstance().lists.filter(b => {
|
||||
return personalList? personalList.roomId !== b.roomId : true;
|
||||
});
|
||||
if (!lists || lists.length <= 0) return <i>{_t("You are not subscribed to any lists")}</i>;
|
||||
if (!lists || lists.length <= 0) return <i>{ _t("You are not subscribed to any lists") }</i>;
|
||||
|
||||
const tiles = [];
|
||||
for (const list of lists) {
|
||||
const room = MatrixClientPeg.get().getRoom(list.roomId);
|
||||
const name = room ? <span>{room.name} (<code>{list.roomId}</code>)</span> : <code>list.roomId</code>;
|
||||
const name = room ? <span>{ room.name } (<code>{ list.roomId }</code>)</span> : <code>list.roomId</code>;
|
||||
tiles.push(
|
||||
<li key={list.roomId} className="mx_MjolnirUserSettingsTab_listItem">
|
||||
<AccessibleButton
|
||||
|
@ -211,24 +211,24 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
onClick={() => this.unsubscribeFromList(list)}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
{_t("Unsubscribe")}
|
||||
{ _t("Unsubscribe") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
kind="primary_sm"
|
||||
onClick={() => this.viewListRules(list)}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
{_t("View rules")}
|
||||
{ _t("View rules") }
|
||||
</AccessibleButton>
|
||||
{name}
|
||||
{ name }
|
||||
</li>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{_t("You are currently subscribed to:")}</p>
|
||||
<ul>{tiles}</ul>
|
||||
<p>{ _t("You are currently subscribed to:") }</p>
|
||||
<ul>{ tiles }</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -238,37 +238,37 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_MjolnirUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Ignored users")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Ignored users") }</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
<span className='warning'>{_t("⚠ These settings are meant for advanced users.")}</span><br />
|
||||
<span className='warning'>{ _t("⚠ These settings are meant for advanced users.") }</span><br />
|
||||
<br />
|
||||
{_t(
|
||||
{ _t(
|
||||
"Add users and servers you want to ignore here. Use asterisks " +
|
||||
"to have %(brand)s match any characters. For example, <code>@bot:*</code> " +
|
||||
"would ignore all users that have the name 'bot' on any server.",
|
||||
{ brand }, { code: (s) => <code>{s}</code> },
|
||||
)}<br />
|
||||
{ brand }, { code: (s) => <code>{ s }</code> },
|
||||
) }<br />
|
||||
<br />
|
||||
{_t(
|
||||
{ _t(
|
||||
"Ignoring people is done through ban lists which contain rules for " +
|
||||
"who to ban. Subscribing to a ban list means the users/servers blocked by " +
|
||||
"that list will be hidden from you.",
|
||||
)}
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Personal ban list")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Personal ban list") }</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t(
|
||||
{ _t(
|
||||
"Your personal ban list holds all the users/servers you personally don't " +
|
||||
"want to see messages from. After ignoring your first user/server, a new room " +
|
||||
"will show up in your room list named 'My Ban List' - stay in this room to keep " +
|
||||
"the ban list in effect.",
|
||||
)}
|
||||
) }
|
||||
</div>
|
||||
<div>
|
||||
{this.renderPersonalBanListRules()}
|
||||
{ this.renderPersonalBanListRules() }
|
||||
</div>
|
||||
<div>
|
||||
<form onSubmit={this.onAddPersonalRule} autoComplete="off">
|
||||
|
@ -285,22 +285,22 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
onClick={this.onAddPersonalRule}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
{_t("Ignore")}
|
||||
{ _t("Ignore") }
|
||||
</AccessibleButton>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Subscribed lists")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Subscribed lists") }</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
<span className='warning'>{_t("Subscribing to a ban list will cause you to join it!")}</span>
|
||||
<span className='warning'>{ _t("Subscribing to a ban list will cause you to join it!") }</span>
|
||||
|
||||
<span>{_t(
|
||||
<span>{ _t(
|
||||
"If this isn't what you want, please use a different tool to ignore users.",
|
||||
)}</span>
|
||||
) }</span>
|
||||
</div>
|
||||
<div>
|
||||
{this.renderSubscribedBanLists()}
|
||||
{ this.renderSubscribedBanLists() }
|
||||
</div>
|
||||
<div>
|
||||
<form onSubmit={this.onSubscribeList} autoComplete="off">
|
||||
|
@ -316,7 +316,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
onClick={this.onSubscribeList}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
{_t("Subscribe")}
|
||||
{ _t("Subscribe") }
|
||||
</AccessibleButton>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -24,7 +24,7 @@ export default class NotificationUserSettingsTab extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_NotificationUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Notifications")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Notifications") }</div>
|
||||
<div className="mx_SettingsTab_section mx_SettingsTab_subsectionText">
|
||||
<Notifications />
|
||||
</div>
|
||||
|
|
|
@ -224,53 +224,53 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta
|
|||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_PreferencesUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Preferences")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Preferences") }</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Room list")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Room list") }</span>
|
||||
{ this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS) }
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Keyboard shortcuts")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Keyboard shortcuts") }</span>
|
||||
<AccessibleButton className="mx_SettingsFlag" onClick={KeyboardShortcuts.toggleDialog}>
|
||||
{ _t("To view all keyboard shortcuts, click here.") }
|
||||
</AccessibleButton>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.KEYBINDINGS_SETTINGS)}
|
||||
{ this.renderGroup(PreferencesUserSettingsTab.KEYBINDINGS_SETTINGS) }
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Displaying time")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.TIME_SETTINGS)}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Displaying time") }</span>
|
||||
{ this.renderGroup(PreferencesUserSettingsTab.TIME_SETTINGS) }
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Composer")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Composer") }</span>
|
||||
{ this.renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS) }
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Code blocks")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.CODE_BLOCKS_SETTINGS)}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Code blocks") }</span>
|
||||
{ this.renderGroup(PreferencesUserSettingsTab.CODE_BLOCKS_SETTINGS) }
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Images, GIFs and videos")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.IMAGES_AND_VIDEOS_SETTINGS)}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Images, GIFs and videos") }</span>
|
||||
{ this.renderGroup(PreferencesUserSettingsTab.IMAGES_AND_VIDEOS_SETTINGS) }
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Timeline")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Timeline") }</span>
|
||||
{ this.renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS) }
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("General")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS)}
|
||||
{minimizeToTrayOption}
|
||||
{autoHideMenuOption}
|
||||
{autoLaunchOption}
|
||||
{warnBeforeExitOption}
|
||||
<span className="mx_SettingsTab_subheading">{ _t("General") }</span>
|
||||
{ this.renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS) }
|
||||
{ minimizeToTrayOption }
|
||||
{ autoHideMenuOption }
|
||||
{ autoLaunchOption }
|
||||
{ warnBeforeExitOption }
|
||||
<Field
|
||||
label={_t('Autocomplete delay (ms)')}
|
||||
type='number'
|
||||
|
|
|
@ -217,10 +217,10 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
importExportButtons = (
|
||||
<div className='mx_SecurityUserSettingsTab_importExportButtons'>
|
||||
<AccessibleButton kind='primary' onClick={this._onExportE2eKeysClicked}>
|
||||
{_t("Export E2E room keys")}
|
||||
{ _t("Export E2E room keys") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind='primary' onClick={this._onImportE2eKeysClicked}>
|
||||
{_t("Import E2E room keys")}
|
||||
{ _t("Import E2E room keys") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
|
@ -237,19 +237,19 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
|
||||
return (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Cryptography")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Cryptography") }</span>
|
||||
<ul className='mx_SettingsTab_subsectionText mx_SecurityUserSettingsTab_deviceInfo'>
|
||||
<li>
|
||||
<label>{_t("Session ID:")}</label>
|
||||
<span><code>{deviceId}</code></span>
|
||||
<label>{ _t("Session ID:") }</label>
|
||||
<span><code>{ deviceId }</code></span>
|
||||
</li>
|
||||
<li>
|
||||
<label>{_t("Session key:")}</label>
|
||||
<span><code><b>{identityKey}</b></code></span>
|
||||
<label>{ _t("Session key:") }</label>
|
||||
<span><code><b>{ identityKey }</b></code></span>
|
||||
</li>
|
||||
</ul>
|
||||
{importExportButtons}
|
||||
{noSendUnverifiedSetting}
|
||||
{ importExportButtons }
|
||||
{ noSendUnverifiedSetting }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -272,9 +272,9 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
|
||||
return (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t('Ignored users')}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t('Ignored users') }</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{userIds}
|
||||
{ userIds }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -291,14 +291,14 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
const onClickReject = this._onRejectAllInvitesClicked.bind(this, invitedRooms);
|
||||
return (
|
||||
<div className='mx_SettingsTab_section mx_SecurityUserSettingsTab_bulkOptions'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t('Bulk options')}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t('Bulk options') }</span>
|
||||
<AccessibleButton onClick={onClickAccept} kind='primary' disabled={this.state.managingInvites}>
|
||||
{_t("Accept all %(invitedRooms)s invites", { invitedRooms: this.state.invitedRoomAmt })}
|
||||
{ _t("Accept all %(invitedRooms)s invites", { invitedRooms: this.state.invitedRoomAmt }) }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton onClick={onClickReject} kind='danger' disabled={this.state.managingInvites}>
|
||||
{_t("Reject all %(invitedRooms)s invites", { invitedRooms: this.state.invitedRoomAmt })}
|
||||
{ _t("Reject all %(invitedRooms)s invites", { invitedRooms: this.state.invitedRoomAmt }) }
|
||||
</AccessibleButton>
|
||||
{this.state.managingInvites ? <InlineSpinner /> : <div />}
|
||||
{ this.state.managingInvites ? <InlineSpinner /> : <div /> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -311,7 +311,7 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
|
||||
const secureBackup = (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className="mx_SettingsTab_subheading">{_t("Secure Backup")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Secure Backup") }</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
<SecureBackupPanel />
|
||||
</div>
|
||||
|
@ -320,7 +320,7 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
|
||||
const eventIndex = (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Message search")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Message search") }</span>
|
||||
<EventIndexPanel />
|
||||
</div>
|
||||
);
|
||||
|
@ -332,7 +332,7 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
const CrossSigningPanel = sdk.getComponent('views.settings.CrossSigningPanel');
|
||||
const crossSigning = (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className="mx_SettingsTab_subheading">{_t("Cross-signing")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Cross-signing") }</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
<CrossSigningPanel />
|
||||
</div>
|
||||
|
@ -350,19 +350,19 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
let privacySection;
|
||||
if (Analytics.canEnable() || CountlyAnalytics.instance.canEnable()) {
|
||||
privacySection = <React.Fragment>
|
||||
<div className="mx_SettingsTab_heading">{_t("Privacy")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Privacy") }</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Analytics")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{ _t("Analytics") }</span>
|
||||
<div className="mx_SettingsTab_subsectionText">
|
||||
{_t(
|
||||
{ _t(
|
||||
"%(brand)s collects anonymous analytics to allow us to improve the application.",
|
||||
{ brand },
|
||||
)}
|
||||
) }
|
||||
|
||||
{_t("Privacy is important to us, so we don't collect any personal or " +
|
||||
"identifiable data for our analytics.")}
|
||||
{ _t("Privacy is important to us, so we don't collect any personal or " +
|
||||
"identifiable data for our analytics.") }
|
||||
<AccessibleButton className="mx_SettingsTab_linkBtn" onClick={Analytics.showDetailsModal}>
|
||||
{_t("Learn more about how we use analytics.")}
|
||||
{ _t("Learn more about how we use analytics.") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<SettingsFlag name="analyticsOptIn" level={SettingLevel.DEVICE} onChange={this._updateAnalytics} />
|
||||
|
@ -379,11 +379,11 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
// only show the section if there's something to show
|
||||
if (ignoreUsersPanel || invitesPanel || e2ePanel) {
|
||||
advancedSection = <>
|
||||
<div className="mx_SettingsTab_heading">{_t("Advanced")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Advanced") }</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
{ignoreUsersPanel}
|
||||
{invitesPanel}
|
||||
{e2ePanel}
|
||||
{ ignoreUsersPanel }
|
||||
{ invitesPanel }
|
||||
{ e2ePanel }
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
|
@ -391,31 +391,31 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_SecurityUserSettingsTab">
|
||||
{warning}
|
||||
<div className="mx_SettingsTab_heading">{_t("Where you’re logged in")}</div>
|
||||
{ warning }
|
||||
<div className="mx_SettingsTab_heading">{ _t("Where you’re logged in") }</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span>
|
||||
{_t(
|
||||
{ _t(
|
||||
"Manage the names of and sign out of your sessions below or " +
|
||||
"<a>verify them in your User Profile</a>.", {},
|
||||
{
|
||||
a: sub => <AccessibleButton kind="link" onClick={this._onGoToUserProfileClick}>
|
||||
{sub}
|
||||
{ sub }
|
||||
</AccessibleButton>,
|
||||
},
|
||||
)}
|
||||
) }
|
||||
</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t("A session's public name is visible to people you communicate with")}
|
||||
{ _t("A session's public name is visible to people you communicate with") }
|
||||
<DevicesPanel />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_SettingsTab_heading">{_t("Encryption")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Encryption") }</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
{secureBackup}
|
||||
{eventIndex}
|
||||
{crossSigning}
|
||||
{this._renderCurrentDeviceInfo()}
|
||||
{ secureBackup }
|
||||
{ eventIndex }
|
||||
{ crossSigning }
|
||||
{ this._renderCurrentDeviceInfo() }
|
||||
</div>
|
||||
{ privacySection }
|
||||
{ advancedSection }
|
||||
|
|
|
@ -130,7 +130,7 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
|
|||
|
||||
private renderDeviceOptions(devices: Array<MediaDeviceInfo>, category: MediaDeviceKindEnum): Array<JSX.Element> {
|
||||
return devices.map((d) => {
|
||||
return (<option key={`${category}-${d.deviceId}`} value={d.deviceId}>{d.label}</option>);
|
||||
return (<option key={`${category}-${d.deviceId}`} value={d.deviceId}>{ d.label }</option>);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -159,9 +159,9 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
|
|||
if (!this.state.mediaDevices) {
|
||||
requestButton = (
|
||||
<div className='mx_VoiceUserSettingsTab_missingMediaPermissions'>
|
||||
<p>{_t("Missing media permissions, click the button below to request.")}</p>
|
||||
<p>{ _t("Missing media permissions, click the button below to request.") }</p>
|
||||
<AccessibleButton onClick={this.requestMediaPermissions} kind="primary">
|
||||
{_t("Request media permissions")}
|
||||
{ _t("Request media permissions") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
|
@ -182,7 +182,7 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
|
|||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_VoiceUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Voice & Video")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Voice & Video") }</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
{ requestButton }
|
||||
{ speakerDropdown }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue