Merge remote-tracking branch 'origin' into posthog-analytics

This commit is contained in:
James Salter 2021-07-28 11:13:22 +01:00
commit bd7e2dee3d
487 changed files with 11710 additions and 6874 deletions

View file

@ -59,7 +59,7 @@ const AvatarSetting = ({ avatarUrl, avatarAltText, avatarName, uploadAvatar, rem
let removeAvatarBtn;
if (avatarUrl && removeAvatar) {
removeAvatarBtn = <AccessibleButton onClick={removeAvatar} kind="link_sm">
{_t("Remove")}
{ _t("Remove") }
</AccessibleButton>;
}
@ -68,13 +68,13 @@ const AvatarSetting = ({ avatarUrl, avatarAltText, avatarName, uploadAvatar, rem
"mx_AvatarSetting_avatar_hovering": isHovering && uploadAvatar,
});
return <div className={avatarClasses}>
{avatarElement}
{ avatarElement }
<div className="mx_AvatarSetting_hover">
<div className="mx_AvatarSetting_hoverBg" />
<span>{_t("Upload")}</span>
<span>{ _t("Upload") }</span>
</div>
{uploadAvatarBtn}
{removeAvatarBtn}
{ uploadAvatarBtn }
{ removeAvatarBtn }
</div>;
};

View file

@ -91,24 +91,24 @@ export default class BridgeTile extends React.PureComponent<IProps> {
let creator = null;
if (content.creator) {
creator = <li>{_t("This bridge was provisioned by <user />.", {}, {
creator = <li>{ _t("This bridge was provisioned by <user />.", {}, {
user: () => <Pill
type={Pill.TYPE_USER_MENTION}
room={this.props.room}
url={makeUserPermalink(content.creator)}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>,
})}</li>;
}) }</li>;
}
const bot = <li>{_t("This bridge is managed by <user />.", {}, {
const bot = <li>{ _t("This bridge is managed by <user />.", {}, {
user: () => <Pill
type={Pill.TYPE_USER_MENTION}
room={this.props.room}
url={makeUserPermalink(content.bridgebot)}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>,
})}</li>;
}) }</li>;
let networkIcon;
@ -119,20 +119,20 @@ export default class BridgeTile extends React.PureComponent<IProps> {
width={48}
height={48}
resizeMethod='crop'
name={ protocolName }
idName={ protocolName }
url={ avatarUrl }
name={protocolName}
idName={protocolName}
url={avatarUrl}
/>;
} else {
networkIcon = <div className="noProtocolIcon"></div>;
networkIcon = <div className="noProtocolIcon" />;
}
let networkItem = null;
if (network) {
const networkName = network.displayname || network.id;
let networkLink = <span>{networkName}</span>;
let networkLink = <span>{ networkName }</span>;
if (typeof network.external_url === "string" && isUrlPermitted(network.external_url)) {
networkLink = (
<a href={network.external_url} target="_blank" rel="noreferrer noopener">{networkName}</a>
<a href={network.external_url} target="_blank" rel="noreferrer noopener">{ networkName }</a>
);
}
networkItem = _t("Workspace: <networkLink/>", {}, {
@ -140,26 +140,26 @@ export default class BridgeTile extends React.PureComponent<IProps> {
});
}
let channelLink = <span>{channelName}</span>;
let channelLink = <span>{ channelName }</span>;
if (typeof channel.external_url === "string" && isUrlPermitted(channel.external_url)) {
channelLink = <a href={channel.external_url} target="_blank" rel="noreferrer noopener">{channelName}</a>;
channelLink = <a href={channel.external_url} target="_blank" rel="noreferrer noopener">{ channelName }</a>;
}
const id = this.props.ev.getId();
return (<li key={id}>
<div className="column-icon">
{networkIcon}
{ networkIcon }
</div>
<div className="column-data">
<h3>{protocolName}</h3>
<h3>{ protocolName }</h3>
<p className="workspace-channel-details">
{networkItem}
<span className="channel">{_t("Channel: <channelLink/>", {}, {
{ networkItem }
<span className="channel">{ _t("Channel: <channelLink/>", {}, {
channelLink: () => channelLink,
})}</span>
}) }</span>
</p>
<ul className="metadata">
{creator} {bot}
{ creator } { bot }
</ul>
</div>
</li>);

View file

@ -148,13 +148,22 @@ export default class ChangeAvatar extends React.Component {
if (this.props.room && !this.avatarSet) {
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
avatarImg = <RoomAvatar
room={this.props.room} width={this.props.width} height={this.props.height} resizeMethod='crop'
room={this.props.room}
width={this.props.width}
height={this.props.height}
resizeMethod='crop'
/>;
} else {
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
// XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
avatarImg = <BaseAvatar width={this.props.width} height={this.props.height} resizeMethod='crop'
name='?' idName={MatrixClientPeg.get().getUserIdLocalpart()} url={this.state.avatarUrl} />;
avatarImg = <BaseAvatar
width={this.props.width}
height={this.props.height}
resizeMethod='crop'
name='?'
idName={MatrixClientPeg.get().getUserIdLocalpart()}
url={this.state.avatarUrl}
/>;
}
let uploadSection;

View file

@ -99,7 +99,7 @@ export default class ChangePassword extends React.Component {
'and re-import them afterwards. ' +
'In future this will be improved.',
) }
{' '}
{ ' ' }
<a href="https://github.com/vector-im/element-web/issues/2671" target="_blank" rel="noreferrer noopener">
https://github.com/vector-im/element-web/issues/2671
</a>

View file

@ -163,29 +163,29 @@ export default class CrossSigningPanel extends React.PureComponent {
let errorSection;
if (error) {
errorSection = <div className="error">{error.toString()}</div>;
errorSection = <div className="error">{ error.toString() }</div>;
}
let summarisedStatus;
if (homeserverSupportsCrossSigning === undefined) {
summarisedStatus = <Spinner />;
} else if (!homeserverSupportsCrossSigning) {
summarisedStatus = <p>{_t(
summarisedStatus = <p>{ _t(
"Your homeserver does not support cross-signing.",
)}</p>;
) }</p>;
} else if (crossSigningReady) {
summarisedStatus = <p> {_t(
summarisedStatus = <p> { _t(
"Cross-signing is ready for use.",
)}</p>;
) }</p>;
} else if (crossSigningPrivateKeysInStorage) {
summarisedStatus = <p>{_t(
summarisedStatus = <p>{ _t(
"Your account has a cross-signing identity in secret storage, " +
"but it is not yet trusted by this session.",
)}</p>;
) }</p>;
} else {
summarisedStatus = <p>{_t(
summarisedStatus = <p>{ _t(
"Cross-signing is not set up.",
)}</p>;
) }</p>;
}
const keysExistAnywhere = (
@ -209,7 +209,7 @@ export default class CrossSigningPanel extends React.PureComponent {
if (!keysExistEverywhere && homeserverSupportsCrossSigning) {
actions.push(
<AccessibleButton key="setup" kind="primary" onClick={this._onBootstrapClick}>
{_t("Set up")}
{ _t("Set up") }
</AccessibleButton>,
);
}
@ -217,7 +217,7 @@ export default class CrossSigningPanel extends React.PureComponent {
if (keysExistAnywhere) {
actions.push(
<AccessibleButton key="reset" kind="danger" onClick={this._resetCrossSigning}>
{_t("Reset")}
{ _t("Reset") }
</AccessibleButton>,
);
}
@ -225,44 +225,44 @@ export default class CrossSigningPanel extends React.PureComponent {
let actionRow;
if (actions.length) {
actionRow = <div className="mx_CrossSigningPanel_buttonRow">
{actions}
{ actions }
</div>;
}
return (
<div>
{summarisedStatus}
{ summarisedStatus }
<details>
<summary>{_t("Advanced")}</summary>
<summary>{ _t("Advanced") }</summary>
<table className="mx_CrossSigningPanel_statusList"><tbody>
<tr>
<td>{_t("Cross-signing public keys:")}</td>
<td>{crossSigningPublicKeysOnDevice ? _t("in memory") : _t("not found")}</td>
<td>{ _t("Cross-signing public keys:") }</td>
<td>{ crossSigningPublicKeysOnDevice ? _t("in memory") : _t("not found") }</td>
</tr>
<tr>
<td>{_t("Cross-signing private keys:")}</td>
<td>{crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found in storage")}</td>
<td>{ _t("Cross-signing private keys:") }</td>
<td>{ crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found in storage") }</td>
</tr>
<tr>
<td>{_t("Master private key:")}</td>
<td>{masterPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
<td>{ _t("Master private key:") }</td>
<td>{ masterPrivateKeyCached ? _t("cached locally") : _t("not found locally") }</td>
</tr>
<tr>
<td>{_t("Self signing private key:")}</td>
<td>{selfSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
<td>{ _t("Self signing private key:") }</td>
<td>{ selfSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally") }</td>
</tr>
<tr>
<td>{_t("User signing private key:")}</td>
<td>{userSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
<td>{ _t("User signing private key:") }</td>
<td>{ userSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally") }</td>
</tr>
<tr>
<td>{_t("Homeserver feature support:")}</td>
<td>{homeserverSupportsCrossSigning ? _t("exists") : _t("not found")}</td>
<td>{ _t("Homeserver feature support:") }</td>
<td>{ homeserverSupportsCrossSigning ? _t("exists") : _t("not found") }</td>
</tr>
</tbody></table>
</details>
{errorSection}
{actionRow}
{ errorSection }
{ actionRow }
</div>
);
}

View file

@ -213,7 +213,7 @@ export default class DevicesPanel extends React.Component {
const deleteButton = this.state.deleting ?
<Spinner w={22} h={22} /> :
<AccessibleButton onClick={this._onDeleteClick} kind="danger_sm">
{ _t("Delete %(count)s sessions", { count: this.state.selectedDevices.length })}
{ _t("Delete %(count)s sessions", { count: this.state.selectedDevices.length }) }
</AccessibleButton>;
const classes = classNames(this.props.className, "mx_DevicesPanel");

View file

@ -25,14 +25,14 @@ const SETTING_MANUALLY_VERIFY_ALL_SESSIONS = "e2ee.manuallyVerifyAllSessions";
const E2eAdvancedPanel = props => {
return <div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Encryption")}</span>
<span className="mx_SettingsTab_subheading">{ _t("Encryption") }</span>
<SettingsFlag name={SETTING_MANUALLY_VERIFY_ALL_SESSIONS}
level={SettingLevel.DEVICE}
/>
<div className="mx_E2eAdvancedPanel_settingLongDescription">{_t(
<div className="mx_E2eAdvancedPanel_settingLongDescription">{ _t(
"Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.",
)}</div>
) }</div>
</div>;
};

View file

@ -152,7 +152,7 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
if (EventIndexPeg.get() !== null) {
eventIndexingSettings = (
<div>
<div className='mx_SettingsTab_subsectionText'>{_t(
<div className='mx_SettingsTab_subsectionText'>{ _t(
"Securely cache encrypted messages locally for them " +
"to appear in search results, using %(size)s to store messages from %(rooms)s rooms.",
{
@ -162,10 +162,10 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
count: this.state.roomCount,
rooms: formatCountLong(this.state.roomCount),
},
)}</div>
) }</div>
<div>
<AccessibleButton kind="primary" onClick={this.onManage}>
{_t("Manage")}
{ _t("Manage") }
</AccessibleButton>
</div>
</div>
@ -173,16 +173,19 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
} else if (!this.state.eventIndexingEnabled && EventIndexPeg.supportIsInstalled()) {
eventIndexingSettings = (
<div>
<div className='mx_SettingsTab_subsectionText'>{_t(
<div className='mx_SettingsTab_subsectionText'>{ _t(
"Securely cache encrypted messages locally for them to " +
"appear in search results.",
)}</div>
) }</div>
<div>
<AccessibleButton kind="primary" disabled={this.state.enabling}
onClick={this.onEnable}>
{_t("Enable")}
<AccessibleButton
kind="primary"
disabled={this.state.enabling}
onClick={this.onEnable}
>
{ _t("Enable") }
</AccessibleButton>
{this.state.enabling ? <InlineSpinner /> : <div />}
{ this.state.enabling ? <InlineSpinner /> : <div /> }
</div>
</div>
);
@ -194,7 +197,7 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
);
eventIndexingSettings = (
<div className='mx_SettingsTab_subsectionText'>{_t(
<div className='mx_SettingsTab_subsectionText'>{ _t(
"%(brand)s is missing some components required for securely " +
"caching encrypted messages locally. If you'd like to " +
"experiment with this feature, build a custom %(brand)s Desktop " +
@ -203,15 +206,17 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
brand,
},
{
nativeLink: sub => <a href={nativeLink}
target="_blank" rel="noreferrer noopener"
>{sub}</a>,
nativeLink: sub => <a
href={nativeLink}
target="_blank"
rel="noreferrer noopener"
>{ sub }</a>,
},
)}</div>
) }</div>
);
} else if (!EventIndexPeg.platformHasSupport()) {
eventIndexingSettings = (
<div className='mx_SettingsTab_subsectionText'>{_t(
<div className='mx_SettingsTab_subsectionText'>{ _t(
"%(brand)s can't securely cache encrypted messages locally " +
"while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> " +
"for encrypted messages to appear in search results.",
@ -219,34 +224,36 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
brand,
},
{
desktopLink: sub => <a href="https://element.io/get-started"
target="_blank" rel="noreferrer noopener"
>{sub}</a>,
desktopLink: sub => <a
href="https://element.io/get-started"
target="_blank"
rel="noreferrer noopener"
>{ sub }</a>,
},
)}</div>
) }</div>
);
} else {
eventIndexingSettings = (
<div className='mx_SettingsTab_subsectionText'>
<p>
{this.state.enabling
{ this.state.enabling
? <InlineSpinner />
: _t("Message search initialisation failed")
}
</p>
{EventIndexPeg.error && (
{ EventIndexPeg.error && (
<details>
<summary>{_t("Advanced")}</summary>
<summary>{ _t("Advanced") }</summary>
<code>
{EventIndexPeg.error.message}
{ EventIndexPeg.error.message }
</code>
<p>
<AccessibleButton key="delete" kind="danger" onClick={this.confirmEventStoreReset}>
{_t("Reset")}
{ _t("Reset") }
</AccessibleButton>
</p>
</details>
)}
) }
</div>
);
}

View file

@ -85,7 +85,7 @@ export default class IntegrationManager extends React.Component {
const Spinner = sdk.getComponent("elements.Spinner");
return (
<div className='mx_IntegrationManager_loading'>
<h3>{_t("Connecting to integration manager...")}</h3>
<h3>{ _t("Connecting to integration manager...") }</h3>
<Spinner />
</div>
);
@ -94,8 +94,8 @@ export default class IntegrationManager extends React.Component {
if (!this.props.connected || this.state.errored) {
return (
<div className='mx_IntegrationManager_error'>
<h3>{_t("Cannot connect to integration manager")}</h3>
<p>{_t("The integration manager is offline or it cannot reach your homeserver.")}</p>
<h3>{ _t("Cannot connect to integration manager") }</h3>
<p>{ _t("The integration manager is offline or it cannot reach your homeserver.") }</p>
</div>
);
}

View file

@ -569,8 +569,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
const rows = this.state.vectorPushRules[category].map(r => <tr key={category + r.ruleId}>
<td>{ r.description }</td>
<td>{ makeRadio(r, VectorState.On) }</td>
<td>{ makeRadio(r, VectorState.Off) }</td>
<td>{ makeRadio(r, VectorState.On) }</td>
<td>{ makeRadio(r, VectorState.Loud) }</td>
</tr>);
@ -594,8 +594,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
<thead>
<tr>
<th>{ sectionName }</th>
<th>{ _t("On") }</th>
<th>{ _t("Off") }</th>
<th>{ _t("On") }</th>
<th>{ _t("Noisy") }</th>
</tr>
</thead>

View file

@ -149,12 +149,12 @@ export default class ProfileSettings extends React.Component {
let hostingSignup = null;
if (hostingSignupLink) {
hostingSignup = <span className="mx_ProfileSettings_hostingSignup">
{_t(
{ _t(
"<a>Upgrade</a> to your own domain", {},
{
a: sub => <a href={hostingSignupLink} target="_blank" rel="noreferrer noopener">{sub}</a>,
a: sub => <a href={hostingSignupLink} target="_blank" rel="noreferrer noopener">{ sub }</a>,
},
)}
) }
<a href={hostingSignupLink} target="_blank" rel="noreferrer noopener">
<img src={require("../../../../res/img/external-link.svg")} width="11" height="10" alt='' />
</a>
@ -172,22 +172,24 @@ export default class ProfileSettings extends React.Component {
>
<input
type="file"
ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
ref={this._avatarUpload}
className="mx_ProfileSettings_avatarUpload"
onChange={this._onAvatarChanged}
accept="image/*"
/>
<div className="mx_ProfileSettings_profile">
<div className="mx_ProfileSettings_controls">
<span className="mx_SettingsTab_subheading">{_t("Profile")}</span>
<span className="mx_SettingsTab_subheading">{ _t("Profile") }</span>
<Field
label={_t("Display Name")}
type="text" value={this.state.displayName}
type="text"
value={this.state.displayName}
autoComplete="off"
onChange={this._onDisplayNameChanged}
/>
<p>
{this.state.userId}
{hostingSignup}
{ this.state.userId }
{ hostingSignup }
</p>
</div>
<AvatarSetting
@ -203,14 +205,14 @@ export default class ProfileSettings extends React.Component {
kind="link"
disabled={!this.state.enableProfileSave}
>
{_t("Cancel")}
{ _t("Cancel") }
</AccessibleButton>
<AccessibleButton
onClick={this._saveProfile}
kind="primary"
disabled={!this.state.enableProfileSave}
>
{_t("Save")}
{ _t("Save") }
</AccessibleButton>
</div>
</form>

View file

@ -221,7 +221,7 @@ export default class SecureBackupPanel extends React.PureComponent {
if (error) {
statusDescription = (
<div className="error">
{_t("Unable to load key backup status")}
{ _t("Unable to load key backup status") }
</div>
);
} else if (loading) {
@ -230,19 +230,19 @@ export default class SecureBackupPanel extends React.PureComponent {
let restoreButtonCaption = _t("Restore from Backup");
if (MatrixClientPeg.get().getKeyBackupEnabled()) {
statusDescription = <p> {_t("This session is backing up your keys. ")}</p>;
statusDescription = <p> { _t("This session is backing up your keys. ") }</p>;
} else {
statusDescription = <>
<p>{_t(
<p>{ _t(
"This session is <b>not backing up your keys</b>, " +
"but you do have an existing backup you can restore from " +
"and add to going forward.", {},
{ b: sub => <b>{sub}</b> },
)}</p>
<p>{_t(
{ b: sub => <b>{ sub }</b> },
) }</p>
<p>{ _t(
"Connect this session to key backup before signing out to avoid " +
"losing any keys that may only be on this session.",
)}</p>
) }</p>
</>;
restoreButtonCaption = _t("Connect this session to Key Backup");
}
@ -253,11 +253,11 @@ export default class SecureBackupPanel extends React.PureComponent {
uploadStatus = "";
} else if (sessionsRemaining > 0) {
uploadStatus = <div>
{_t("Backing up %(sessionsRemaining)s keys...", { sessionsRemaining })} <br />
{ _t("Backing up %(sessionsRemaining)s keys...", { sessionsRemaining }) } <br />
</div>;
} else {
uploadStatus = <div>
{_t("All keys backed up")} <br />
{ _t("All keys backed up") } <br />
</div>;
}
@ -265,13 +265,13 @@ export default class SecureBackupPanel extends React.PureComponent {
const deviceName = sig.device ? (sig.device.getDisplayName() || sig.device.deviceId) : null;
const validity = sub =>
<span className={sig.valid ? 'mx_SecureBackupPanel_sigValid' : 'mx_SecureBackupPanel_sigInvalid'}>
{sub}
{ sub }
</span>;
const verify = sub =>
<span className={sig.device && sig.deviceTrust.isVerified() ? 'mx_SecureBackupPanel_deviceVerified' : 'mx_SecureBackupPanel_deviceNotVerified'}>
{sub}
{ sub }
</span>;
const device = sub => <span className="mx_SecureBackupPanel_deviceName">{deviceName}</span>;
const device = sub => <span className="mx_SecureBackupPanel_deviceName">{ deviceName }</span>;
const fromThisDevice = (
sig.device &&
sig.device.getFingerprint() === MatrixClientPeg.get().getDeviceEd25519Key()
@ -339,7 +339,7 @@ export default class SecureBackupPanel extends React.PureComponent {
}
return <div key={i}>
{sigStatus}
{ sigStatus }
</div>;
});
if (backupSigStatus.sigs.length === 0) {
@ -353,45 +353,45 @@ export default class SecureBackupPanel extends React.PureComponent {
extraDetailsTableRows = <>
<tr>
<td>{_t("Backup version:")}</td>
<td>{backupInfo.version}</td>
<td>{ _t("Backup version:") }</td>
<td>{ backupInfo.version }</td>
</tr>
<tr>
<td>{_t("Algorithm:")}</td>
<td>{backupInfo.algorithm}</td>
<td>{ _t("Algorithm:") }</td>
<td>{ backupInfo.algorithm }</td>
</tr>
</>;
extraDetails = <>
{uploadStatus}
<div>{backupSigStatuses}</div>
<div>{trustedLocally}</div>
{ uploadStatus }
<div>{ backupSigStatuses }</div>
<div>{ trustedLocally }</div>
</>;
actions.push(
<AccessibleButton key="restore" kind="primary" onClick={this._restoreBackup}>
{restoreButtonCaption}
{ restoreButtonCaption }
</AccessibleButton>,
);
if (!isSecureBackupRequired()) {
actions.push(
<AccessibleButton key="delete" kind="danger" onClick={this._deleteBackup}>
{_t("Delete Backup")}
{ _t("Delete Backup") }
</AccessibleButton>,
);
}
} else {
statusDescription = <>
<p>{_t(
<p>{ _t(
"Your keys are <b>not being backed up from this session</b>.", {},
{ b: sub => <b>{sub}</b> },
)}</p>
<p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
{ b: sub => <b>{ sub }</b> },
) }</p>
<p>{ _t("Back up your keys before signing out to avoid losing them.") }</p>
</>;
actions.push(
<AccessibleButton key="setup" kind="primary" onClick={this._startNewBackup}>
{_t("Set up")}
{ _t("Set up") }
</AccessibleButton>,
);
}
@ -399,7 +399,7 @@ export default class SecureBackupPanel extends React.PureComponent {
if (secretStorageKeyInAccount) {
actions.push(
<AccessibleButton key="reset" kind="danger" onClick={this._resetSecretStorage}>
{_t("Reset")}
{ _t("Reset") }
</AccessibleButton>,
);
}
@ -417,47 +417,47 @@ export default class SecureBackupPanel extends React.PureComponent {
let actionRow;
if (actions.length) {
actionRow = <div className="mx_SecureBackupPanel_buttonRow">
{actions}
{ actions }
</div>;
}
return (
<div>
<p>{_t(
<p>{ _t(
"Back up your encryption keys with your account data in case you " +
"lose access to your sessions. Your keys will be secured with a " +
"unique Security Key.",
)}</p>
{statusDescription}
) }</p>
{ statusDescription }
<details>
<summary>{_t("Advanced")}</summary>
<summary>{ _t("Advanced") }</summary>
<table className="mx_SecureBackupPanel_statusList"><tbody>
<tr>
<td>{_t("Backup key stored:")}</td>
<td>{ _t("Backup key stored:") }</td>
<td>{
backupKeyStored === true ? _t("in secret storage") : _t("not stored")
}</td>
</tr>
<tr>
<td>{_t("Backup key cached:")}</td>
<td>{ _t("Backup key cached:") }</td>
<td>
{backupKeyCached ? _t("cached locally") : _t("not found locally")}
{backupKeyWellFormedText}
{ backupKeyCached ? _t("cached locally") : _t("not found locally") }
{ backupKeyWellFormedText }
</td>
</tr>
<tr>
<td>{_t("Secret storage public key:")}</td>
<td>{secretStorageKeyInAccount ? _t("in account data") : _t("not found")}</td>
<td>{ _t("Secret storage public key:") }</td>
<td>{ secretStorageKeyInAccount ? _t("in account data") : _t("not found") }</td>
</tr>
<tr>
<td>{_t("Secret storage:")}</td>
<td>{secretStorageReady ? _t("ready") : _t("not ready")}</td>
<td>{ _t("Secret storage:") }</td>
<td>{ secretStorageReady ? _t("ready") : _t("not ready") }</td>
</tr>
{extraDetailsTableRows}
{ extraDetailsTableRows }
</tbody></table>
{extraDetails}
{ extraDetails }
</details>
{actionRow}
{ actionRow }
</div>
);
}

View file

@ -134,7 +134,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
{ _t("Checking server") }
</div>;
} else if (this.state.error) {
return <span className='warning'>{this.state.error}</span>;
return <span className='warning'>{ this.state.error }</span>;
} else {
return null;
}
@ -193,8 +193,8 @@ export default class SetIdServer extends React.Component<IProps, IState> {
"Disconnect from the identity server <current /> and " +
"connect to <new /> instead?", {},
{
current: sub => <b>{abbreviateUrl(currentClientIdServer)}</b>,
new: sub => <b>{abbreviateUrl(idServer)}</b>,
current: sub => <b>{ abbreviateUrl(currentClientIdServer) }</b>,
new: sub => <b>{ abbreviateUrl(idServer) }</b>,
},
),
button: _t("Continue"),
@ -224,10 +224,10 @@ export default class SetIdServer extends React.Component<IProps, IState> {
description: (
<div>
<span className="warning">
{_t("The identity server you have chosen does not have any terms of service.")}
{ _t("The identity server you have chosen does not have any terms of service.") }
</span>
<span>
&nbsp;{_t("Only continue if you trust the owner of the server.")}
&nbsp;{ _t("Only continue if you trust the owner of the server.") }
</span>
</div>
),
@ -243,7 +243,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
title: _t("Disconnect identity server"),
unboundMessage: _t(
"Disconnect from the identity server <idserver />?", {},
{ idserver: sub => <b>{abbreviateUrl(this.state.currentClientIdServer)}</b> },
{ idserver: sub => <b>{ abbreviateUrl(this.state.currentClientIdServer) }</b> },
),
button: _t("Disconnect"),
});
@ -278,41 +278,41 @@ export default class SetIdServer extends React.Component<IProps, IState> {
let message;
let danger = false;
const messageElements = {
idserver: sub => <b>{abbreviateUrl(currentClientIdServer)}</b>,
b: sub => <b>{sub}</b>,
idserver: sub => <b>{ abbreviateUrl(currentClientIdServer) }</b>,
b: sub => <b>{ sub }</b>,
};
if (!currentServerReachable) {
message = <div>
<p>{_t(
<p>{ _t(
"You should <b>remove your personal data</b> from identity server " +
"<idserver /> before disconnecting. Unfortunately, identity server " +
"<idserver /> is currently offline or cannot be reached.",
{}, messageElements,
)}</p>
<p>{_t("You should:")}</p>
) }</p>
<p>{ _t("You should:") }</p>
<ul>
<li>{_t(
<li>{ _t(
"check your browser plugins for anything that might block " +
"the identity server (such as Privacy Badger)",
)}</li>
<li>{_t("contact the administrators of identity server <idserver />", {}, {
) }</li>
<li>{ _t("contact the administrators of identity server <idserver />", {}, {
idserver: messageElements.idserver,
})}</li>
<li>{_t("wait and try again later")}</li>
}) }</li>
<li>{ _t("wait and try again later") }</li>
</ul>
</div>;
danger = true;
button = _t("Disconnect anyway");
} else if (boundThreepids.length) {
message = <div>
<p>{_t(
<p>{ _t(
"You are still <b>sharing your personal data</b> on the identity " +
"server <idserver />.", {}, messageElements,
)}</p>
<p>{_t(
) }</p>
<p>{ _t(
"We recommend that you remove your email addresses and phone numbers " +
"from the identity server before disconnecting.",
)}</p>
) }</p>
</div>;
danger = true;
button = _t("Disconnect anyway");
@ -361,13 +361,13 @@ export default class SetIdServer extends React.Component<IProps, IState> {
"You are currently using <server></server> to discover and be discoverable by " +
"existing contacts you know. You can change your identity server below.",
{},
{ server: sub => <b>{abbreviateUrl(idServerUrl)}</b> },
{ server: sub => <b>{ abbreviateUrl(idServerUrl) }</b> },
);
if (this.props.missingTerms) {
bodyText = _t(
"If you don't want to use <server /> to discover and be discoverable by existing " +
"contacts you know, enter another identity server below.",
{}, { server: sub => <b>{abbreviateUrl(idServerUrl)}</b> },
{}, { server: sub => <b>{ abbreviateUrl(idServerUrl) }</b> },
);
}
} else {
@ -399,9 +399,9 @@ export default class SetIdServer extends React.Component<IProps, IState> {
discoButtonContent = <InlineSpinner />;
}
discoSection = <div>
<span className="mx_SettingsTab_subsectionText">{discoBodyText}</span>
<span className="mx_SettingsTab_subsectionText">{ discoBodyText }</span>
<AccessibleButton onClick={this.onDisconnectClicked} kind="danger_sm">
{discoButtonContent}
{ discoButtonContent }
</AccessibleButton>
</div>;
}
@ -409,10 +409,10 @@ export default class SetIdServer extends React.Component<IProps, IState> {
return (
<form className="mx_SettingsTab_section mx_SetIdServer" onSubmit={this.checkIdServer}>
<span className="mx_SettingsTab_subheading">
{sectionTitle}
{ sectionTitle }
</span>
<span className="mx_SettingsTab_subsectionText">
{bodyText}
{ bodyText }
</span>
<Field
label={_t("Enter a new identity server")}
@ -426,11 +426,13 @@ export default class SetIdServer extends React.Component<IProps, IState> {
disabled={this.state.busy}
forceValidity={this.state.error ? false : null}
/>
<AccessibleButton type="submit" kind="primary_sm"
<AccessibleButton
type="submit"
kind="primary_sm"
onClick={this.checkIdServer}
disabled={!this.idServerChangeEnabled()}
>{_t("Change")}</AccessibleButton>
{discoSection}
>{ _t("Change") }</AccessibleButton>
{ discoSection }
</form>
);
}

View file

@ -68,7 +68,7 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
"Use an integration manager <b>(%(serverName)s)</b> to manage bots, widgets, " +
"and sticker packs.",
{ serverName: currentManager.name },
{ b: sub => <b>{sub}</b> },
{ b: sub => <b>{ sub }</b> },
);
} else {
bodyText = _t("Use an integration manager to manage bots, widgets, and sticker packs.");
@ -77,18 +77,18 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
return (
<div className='mx_SetIntegrationManager'>
<div className="mx_SettingsTab_heading">
<span>{_t("Manage integrations")}</span>
<span className="mx_SettingsTab_subheading">{managerName}</span>
<span>{ _t("Manage integrations") }</span>
<span className="mx_SettingsTab_subheading">{ managerName }</span>
<ToggleSwitch checked={this.state.provisioningEnabled} onChange={this.onProvisioningToggled} />
</div>
<span className="mx_SettingsTab_subsectionText">
{bodyText}
{ bodyText }
<br />
<br />
{_t(
{ _t(
"Integration managers receive configuration data, and can modify widgets, " +
"send room invites, and set power levels on your behalf.",
)}
) }
</span>
</div>
);

View file

@ -35,7 +35,7 @@ interface SpellCheckLanguagesIState {
}
export class ExistingSpellCheckLanguage extends React.Component<ExistingSpellCheckLanguageIProps> {
_onRemove = (e) => {
private onRemove = (e) => {
e.stopPropagation();
e.preventDefault();
@ -45,9 +45,9 @@ export class ExistingSpellCheckLanguage extends React.Component<ExistingSpellChe
render() {
return (
<div className="mx_ExistingSpellCheckLanguage">
<span className="mx_ExistingSpellCheckLanguage_language">{this.props.language}</span>
<AccessibleButton onClick={this._onRemove} kind="danger_sm">
{_t("Remove")}
<span className="mx_ExistingSpellCheckLanguage_language">{ this.props.language }</span>
<AccessibleButton onClick={this.onRemove} kind="danger_sm">
{ _t("Remove") }
</AccessibleButton>
</div>
);
@ -63,12 +63,12 @@ export default class SpellCheckLanguages extends React.Component<SpellCheckLangu
};
}
_onRemoved = (language) => {
private onRemoved = (language: string) => {
const languages = this.props.languages.filter((e) => e !== language);
this.props.onLanguagesChange(languages);
};
_onAddClick = (e) => {
private onAddClick = (e) => {
e.stopPropagation();
e.preventDefault();
@ -81,31 +81,31 @@ export default class SpellCheckLanguages extends React.Component<SpellCheckLangu
this.props.onLanguagesChange(this.props.languages);
};
_onNewLanguageChange = (language: string) => {
private onNewLanguageChange = (language: string) => {
if (this.state.newLanguage === language) return;
this.setState({ newLanguage: language });
};
render() {
const existingSpellCheckLanguages = this.props.languages.map((e) => {
return <ExistingSpellCheckLanguage language={e} onRemoved={this._onRemoved} key={e} />;
return <ExistingSpellCheckLanguage language={e} onRemoved={this.onRemoved} key={e} />;
});
const addButton = (
<AccessibleButton onClick={this._onAddClick} kind="primary">
{_t("Add")}
<AccessibleButton onClick={this.onAddClick} kind="primary">
{ _t("Add") }
</AccessibleButton>
);
return (
<div className="mx_SpellCheckLanguages">
{existingSpellCheckLanguages}
<form onSubmit={this._onAddClick} noValidate={true}>
{ existingSpellCheckLanguages }
<form onSubmit={this.onAddClick} noValidate={true}>
<SpellCheckLanguagesDropdown
className="mx_GeneralUserSettingsTab_spellCheckLanguageInput"
value={this.state.newLanguage}
onOptionChange={this._onNewLanguageChange} />
{addButton}
onOptionChange={this.onNewLanguageChange} />
{ addButton }
</form>
</div>
);

View file

@ -42,7 +42,7 @@ function getStatusText(status: UpdateCheckStatus, errorDetail?: string) {
return _t('Downloading update...');
case UpdateCheckStatus.Ready:
return _t("New version available. <a>Update now.</a>", {}, {
a: sub => <AccessibleButton kind="link" onClick={installUpdate}>{sub}</AccessibleButton>,
a: sub => <AccessibleButton kind="link" onClick={installUpdate}>{ sub }</AccessibleButton>,
});
}
}
@ -72,14 +72,14 @@ const UpdateCheckButton = () => {
let suffix;
if (state) {
suffix = <span className="mx_UpdateCheckButton_summary">
{getStatusText(state.status, state.detail)}
{busy && <InlineSpinner />}
{ getStatusText(state.status, state.detail) }
{ busy && <InlineSpinner /> }
</span>;
}
return <React.Fragment>
<AccessibleButton onClick={onCheckForUpdateClick} kind="primary" disabled={busy}>
{_t("Check for update")}
{ _t("Check for update") }
</AccessibleButton>
{ suffix }
</React.Fragment>;

View file

@ -88,21 +88,21 @@ export class ExistingEmailAddress extends React.Component {
return (
<div className="mx_ExistingEmailAddress">
<span className="mx_ExistingEmailAddress_promptText">
{_t("Remove %(email)s?", { email: this.props.email.address } )}
{ _t("Remove %(email)s?", { email: this.props.email.address } ) }
</span>
<AccessibleButton
onClick={this._onActuallyRemove}
kind="danger_sm"
className="mx_ExistingEmailAddress_confirmBtn"
>
{_t("Remove")}
{ _t("Remove") }
</AccessibleButton>
<AccessibleButton
onClick={this._onDontRemove}
kind="link_sm"
className="mx_ExistingEmailAddress_confirmBtn"
>
{_t("Cancel")}
{ _t("Cancel") }
</AccessibleButton>
</div>
);
@ -110,9 +110,9 @@ export class ExistingEmailAddress extends React.Component {
return (
<div className="mx_ExistingEmailAddress">
<span className="mx_ExistingEmailAddress_email">{this.props.email.address}</span>
<span className="mx_ExistingEmailAddress_email">{ this.props.email.address }</span>
<AccessibleButton onClick={this._onRemove} kind="danger_sm">
{_t("Remove")}
{ _t("Remove") }
</AccessibleButton>
</div>
);
@ -229,19 +229,19 @@ export default class EmailAddresses extends React.Component {
let addButton = (
<AccessibleButton onClick={this._onAddClick} kind="primary">
{_t("Add")}
{ _t("Add") }
</AccessibleButton>
);
if (this.state.verifying) {
addButton = (
<div>
<div>{_t("We've sent you an email to verify your address. Please follow the instructions there and then click the button below.")}</div>
<div>{ _t("We've sent you an email to verify your address. Please follow the instructions there and then click the button below.") }</div>
<AccessibleButton
onClick={this._onContinueClick}
kind="primary"
disabled={this.state.continueDisabled}
>
{_t("Continue")}
{ _t("Continue") }
</AccessibleButton>
</div>
);
@ -249,7 +249,7 @@ export default class EmailAddresses extends React.Component {
return (
<div className="mx_EmailAddresses">
{existingEmailElements}
{ existingEmailElements }
<form
onSubmit={this._onAddClick}
autoComplete="off"
@ -264,7 +264,7 @@ export default class EmailAddresses extends React.Component {
value={this.state.newEmailAddress}
onChange={this._onChangeNewEmailAddress}
/>
{addButton}
{ addButton }
</form>
</div>
);

View file

@ -83,21 +83,21 @@ export class ExistingPhoneNumber extends React.Component {
return (
<div className="mx_ExistingPhoneNumber">
<span className="mx_ExistingPhoneNumber_promptText">
{_t("Remove %(phone)s?", { phone: this.props.msisdn.address })}
{ _t("Remove %(phone)s?", { phone: this.props.msisdn.address }) }
</span>
<AccessibleButton
onClick={this._onActuallyRemove}
kind="danger_sm"
className="mx_ExistingPhoneNumber_confirmBtn"
>
{_t("Remove")}
{ _t("Remove") }
</AccessibleButton>
<AccessibleButton
onClick={this._onDontRemove}
kind="link_sm"
className="mx_ExistingPhoneNumber_confirmBtn"
>
{_t("Cancel")}
{ _t("Cancel") }
</AccessibleButton>
</div>
);
@ -105,9 +105,9 @@ export class ExistingPhoneNumber extends React.Component {
return (
<div className="mx_ExistingPhoneNumber">
<span className="mx_ExistingPhoneNumber_address">+{this.props.msisdn.address}</span>
<span className="mx_ExistingPhoneNumber_address">+{ this.props.msisdn.address }</span>
<AccessibleButton onClick={this._onRemove} kind="danger_sm">
{_t("Remove")}
{ _t("Remove") }
</AccessibleButton>
</div>
);
@ -230,7 +230,7 @@ export default class PhoneNumbers extends React.Component {
let addVerifySection = (
<AccessibleButton onClick={this._onAddClick} kind="primary">
{_t("Add")}
{ _t("Add") }
</AccessibleButton>
);
if (this.state.verifying) {
@ -238,10 +238,10 @@ export default class PhoneNumbers extends React.Component {
addVerifySection = (
<div>
<div>
{_t("A text message has been sent to +%(msisdn)s. " +
"Please enter the verification code it contains.", { msisdn: msisdn })}
{ _t("A text message has been sent to +%(msisdn)s. " +
"Please enter the verification code it contains.", { msisdn: msisdn }) }
<br />
{this.state.verifyError}
{ this.state.verifyError }
</div>
<form onSubmit={this._onContinueClick} autoComplete="off" noValidate={true}>
<Field
@ -257,7 +257,7 @@ export default class PhoneNumbers extends React.Component {
kind="primary"
disabled={this.state.continueDisabled}
>
{_t("Continue")}
{ _t("Continue") }
</AccessibleButton>
</form>
</div>
@ -274,7 +274,7 @@ export default class PhoneNumbers extends React.Component {
return (
<div className="mx_PhoneNumbers">
{existingPhoneElements}
{ existingPhoneElements }
<form onSubmit={this._onAddClick} autoComplete="off" noValidate={true} className="mx_PhoneNumbers_new">
<div className="mx_PhoneNumbers_input">
<Field
@ -288,7 +288,7 @@ export default class PhoneNumbers extends React.Component {
/>
</div>
</form>
{addVerifySection}
{ addVerifySection }
</div>
);
}

View file

@ -198,14 +198,14 @@ export class EmailAddress extends React.Component {
let status;
if (verifying) {
status = <span>
{_t("Verify the link in your inbox")}
{ _t("Verify the link in your inbox") }
<AccessibleButton
className="mx_ExistingEmailAddress_confirmBtn"
kind="primary_sm"
onClick={this.onContinueClick}
disabled={this.state.continueDisabled}
>
{_t("Complete")}
{ _t("Complete") }
</AccessibleButton>
</span>;
} else if (bound) {
@ -214,7 +214,7 @@ export class EmailAddress extends React.Component {
kind="danger_sm"
onClick={this.onRevokeClick}
>
{_t("Revoke")}
{ _t("Revoke") }
</AccessibleButton>;
} else {
status = <AccessibleButton
@ -222,14 +222,14 @@ export class EmailAddress extends React.Component {
kind="primary_sm"
onClick={this.onShareClick}
>
{_t("Share")}
{ _t("Share") }
</AccessibleButton>;
}
return (
<div className="mx_ExistingEmailAddress">
<span className="mx_ExistingEmailAddress_email">{address}</span>
{status}
<span className="mx_ExistingEmailAddress_email">{ address }</span>
{ status }
</div>
);
}
@ -249,13 +249,13 @@ export default class EmailAddresses extends React.Component {
});
} else {
content = <span className="mx_SettingsTab_subsectionText">
{_t("Discovery options will appear once you have added an email above.")}
{ _t("Discovery options will appear once you have added an email above.") }
</span>;
}
return (
<div className="mx_EmailAddresses">
{content}
{ content }
</div>
);
}

View file

@ -205,9 +205,9 @@ export class PhoneNumber extends React.Component {
if (verifying) {
status = <span className="mx_ExistingPhoneNumber_verification">
<span>
{_t("Please enter verification code sent via text.")}
{ _t("Please enter verification code sent via text.") }
<br />
{this.state.verifyError}
{ this.state.verifyError }
</span>
<form onSubmit={this.onContinueClick} autoComplete="off" noValidate={true}>
<Field
@ -226,7 +226,7 @@ export class PhoneNumber extends React.Component {
kind="danger_sm"
onClick={this.onRevokeClick}
>
{_t("Revoke")}
{ _t("Revoke") }
</AccessibleButton>;
} else {
status = <AccessibleButton
@ -234,14 +234,14 @@ export class PhoneNumber extends React.Component {
kind="primary_sm"
onClick={this.onShareClick}
>
{_t("Share")}
{ _t("Share") }
</AccessibleButton>;
}
return (
<div className="mx_ExistingPhoneNumber">
<span className="mx_ExistingPhoneNumber_address">+{address}</span>
{status}
<span className="mx_ExistingPhoneNumber_address">+{ address }</span>
{ status }
</div>
);
}
@ -261,13 +261,13 @@ export default class PhoneNumbers extends React.Component {
});
} else {
content = <span className="mx_SettingsTab_subsectionText">
{_t("Discovery options will appear once you have added a phone number above.")}
{ _t("Discovery options will appear once you have added a phone number above.") }
</span>;
}
return (
<div className="mx_PhoneNumbers">
{content}
{ content }
</div>
);
}

View file

@ -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>

View file

@ -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 isnt 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>
);

View file

@ -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') }

View file

@ -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>

View file

@ -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>
);

View file

@ -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.")}&nbsp;
<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>
);
}

View file

@ -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>
);
}

View file

@ -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>

View file

@ -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>
);

View file

@ -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, &nbsp; 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 ©&nbsp;
<a href="https://www.flickr.com/golan" rel="noreferrer noopener"
target="_blank">Jesús Roncero</a> used under the terms of&nbsp;
<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 ©&nbsp;
<a href="https://www.flickr.com/golan" rel="noreferrer noopener" target="_blank">
Jesús Roncero
</a> used under the terms of&nbsp;
<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 ©&nbsp;
<a href="https://mozilla.org" rel="noreferrer noopener"
target="_blank">Mozilla Foundation</a> used under the terms of&nbsp;
<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 ©&nbsp;
<a href="https://mozilla.org" rel="noreferrer noopener" target="_blank">
Mozilla Foundation
</a> used under the terms of&nbsp;
<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 ©&nbsp;
<a href="https://twemoji.twitter.com/" rel="noreferrer noopener"
target="_blank">Twitter, Inc and other contributors</a> used under the terms of&nbsp;
<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 ©&nbsp;
<a href="https://twemoji.twitter.com/" rel="noreferrer noopener" target="_blank">
Twitter, Inc and other contributors
</a> used under the terms of&nbsp;
<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>

View file

@ -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>;
},
})
}

View file

@ -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>&nbsp;
<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>&nbsp;
<AccessibleButton
kind="primary_sm"
onClick={() => this.viewListRules(list)}
disabled={this.state.busy}
>
{_t("View rules")}
{ _t("View rules") }
</AccessibleButton>&nbsp;
{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>
&nbsp;
<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>

View file

@ -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>

View file

@ -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'

View file

@ -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 },
)}
) }
&nbsp;
{_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 youre logged in")}</div>
{ warning }
<div className="mx_SettingsTab_heading">{ _t("Where youre 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 }

View file

@ -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 }