Merge branch 'develop' into show-invite-reasons

This commit is contained in:
Robin Townsend 2021-03-31 15:47:26 -04:00
commit 2477258249
540 changed files with 19085 additions and 3868 deletions

View file

@ -16,9 +16,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
import {_t} from "../../../languageHandler";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import Pill from "../elements/Pill";
import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import BaseAvatar from "../avatars/BaseAvatar";
@ -26,6 +24,8 @@ import SettingsStore from "../../../settings/SettingsStore";
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { isUrlPermitted } from '../../../HtmlUtils';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
interface IProps {
ev: MatrixEvent;
@ -64,6 +64,7 @@ interface IBridgeStateEvent {
};
}
@replaceableComponent("views.settings.BridgeTile")
export default class BridgeTile extends React.PureComponent<IProps> {
static propTypes = {
ev: PropTypes.object.isRequired,
@ -112,10 +113,7 @@ export default class BridgeTile extends React.PureComponent<IProps> {
let networkIcon;
if (protocol.avatar_url) {
const avatarUrl = getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(),
protocol.avatar_url, 64, 64, "crop",
);
const avatarUrl = mediaFromMxc(protocol.avatar_url).getSquareThumbnailHttp(64);
networkIcon = <BaseAvatar className="protocol-icon"
width={48}

View file

@ -20,7 +20,10 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import Spinner from '../elements/Spinner';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
@replaceableComponent("views.settings.ChangeAvatar")
export default class ChangeAvatar extends React.Component {
static propTypes = {
initialAvatarUrl: PropTypes.string,
@ -115,7 +118,7 @@ export default class ChangeAvatar extends React.Component {
httpPromise.then(function() {
self.setState({
phase: ChangeAvatar.Phases.Display,
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl),
avatarUrl: mediaFromMxc(newUrl).srcHttp,
});
}, function(error) {
self.setState({

View file

@ -20,7 +20,9 @@ import React from 'react';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.settings.ChangeDisplayName")
export default class ChangeDisplayName extends React.Component {
_getDisplayName = async () => {
const cli = MatrixClientPeg.get();

View file

@ -27,6 +27,7 @@ import * as sdk from "../../../index";
import Modal from "../../../Modal";
import PassphraseField from "../auth/PassphraseField";
import CountlyAnalytics from "../../../CountlyAnalytics";
import {replaceableComponent} from "../../../utils/replaceableComponent";
const FIELD_OLD_PASSWORD = 'field_old_password';
const FIELD_NEW_PASSWORD = 'field_new_password';
@ -34,6 +35,7 @@ const FIELD_NEW_PASSWORD_CONFIRM = 'field_new_password_confirm';
const PASSWORD_MIN_SCORE = 3; // safely unguessable: moderate protection from offline slow-hash scenario.
@replaceableComponent("views.settings.ChangePassword")
export default class ChangePassword extends React.Component {
static propTypes = {
onFinished: PropTypes.func,

View file

@ -23,7 +23,9 @@ import Modal from '../../../Modal';
import Spinner from '../elements/Spinner';
import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog';
import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.settings.CrossSigningPanel")
export default class CrossSigningPanel extends React.PureComponent {
constructor(props) {
super(props);

View file

@ -24,7 +24,9 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
import {SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.settings.DevicesPanel")
export default class DevicesPanel extends React.Component {
constructor(props) {
super(props);

View file

@ -22,7 +22,9 @@ import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {formatDate} from '../../../DateUtils';
import StyledCheckbox from '../elements/StyledCheckbox';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.settings.DevicesPanelEntry")
export default class DevicesPanelEntry extends React.Component {
constructor(props) {
super(props);

View file

@ -25,7 +25,9 @@ import AccessibleButton from "../elements/AccessibleButton";
import {formatBytes, formatCountLong} from "../../../utils/FormattingUtils";
import EventIndexPeg from "../../../indexing/EventIndexPeg";
import {SettingLevel} from "../../../settings/SettingLevel";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.settings.EventIndexPanel")
export default class EventIndexPanel extends React.Component {
constructor() {
super();
@ -165,7 +167,7 @@ export default class EventIndexPanel extends React.Component {
);
} else if (EventIndexPeg.platformHasSupport() && !EventIndexPeg.supportIsInstalled()) {
const nativeLink = (
"https://github.com/vector-im/element-web/blob/develop/" +
"https://github.com/vector-im/element-desktop/blob/develop/" +
"docs/native-node-modules.md#" +
"adding-seshat-for-search-in-e2e-encrypted-rooms"
);
@ -188,7 +190,7 @@ export default class EventIndexPanel extends React.Component {
}
</div>
);
} else {
} else if (!EventIndexPeg.platformHasSupport()) {
eventIndexingSettings = (
<div className='mx_SettingsTab_subsectionText'>
{
@ -206,6 +208,23 @@ export default class EventIndexPanel extends React.Component {
}
</div>
);
} else {
eventIndexingSettings = (
<div className='mx_SettingsTab_subsectionText'>
<p>
{_t("Message search initialisation failed")}
</p>
{EventIndexPeg.error && (
<details>
<summary>{_t("Advanced")}</summary>
<code>
{EventIndexPeg.error.message}
</code>
</details>
)}
</div>
);
}
return eventIndexingSettings;

View file

@ -21,7 +21,9 @@ import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher';
import {Key} from "../../../Keyboard";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.settings.IntegrationManager")
export default class IntegrationManager extends React.Component {
static propTypes = {
// false to display an error saying that we couldn't connect to the integration manager

View file

@ -32,6 +32,7 @@ import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import AccessibleButton from "../elements/AccessibleButton";
import {SettingLevel} from "../../../settings/SettingLevel";
import {UIFeature} from "../../../settings/UIFeature";
import {replaceableComponent} from "../../../utils/replaceableComponent";
// TODO: this "view" component still has far too much application logic in it,
// which should be factored out to other files.
@ -65,6 +66,7 @@ function portLegacyActions(actions) {
}
}
@replaceableComponent("views.settings.Notifications")
export default class Notifications extends React.Component {
static phases = {
LOADING: "LOADING", // The component is loading or sending data to the hs

View file

@ -23,14 +23,17 @@ import * as sdk from "../../../index";
import {OwnProfileStore} from "../../../stores/OwnProfileStore";
import Modal from "../../../Modal";
import ErrorDialog from "../dialogs/ErrorDialog";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
@replaceableComponent("views.settings.ProfileSettings")
export default class ProfileSettings extends React.Component {
constructor() {
super();
const client = MatrixClientPeg.get();
let avatarUrl = OwnProfileStore.instance.avatarMxc;
if (avatarUrl) avatarUrl = client.mxcUrlToHttp(avatarUrl, 96, 96, 'crop', false);
if (avatarUrl) avatarUrl = mediaFromMxc(avatarUrl).getSquareThumbnailHttp(96);
this.state = {
userId: client.getUserId(),
originalDisplayName: OwnProfileStore.instance.displayName,
@ -81,10 +84,12 @@ export default class ProfileSettings extends React.Component {
const client = MatrixClientPeg.get();
const newState = {};
const displayName = this.state.displayName.trim();
try {
if (this.state.originalDisplayName !== this.state.displayName) {
await client.setDisplayName(this.state.displayName);
newState.originalDisplayName = this.state.displayName;
await client.setDisplayName(displayName);
newState.originalDisplayName = displayName;
newState.displayName = displayName;
}
if (this.state.avatarFile) {
@ -93,7 +98,7 @@ export default class ProfileSettings extends React.Component {
` (${this.state.avatarFile.size}) bytes`);
const uri = await client.uploadContent(this.state.avatarFile);
await client.setAvatarUrl(uri);
newState.avatarUrl = client.mxcUrlToHttp(uri, 96, 96, 'crop', false);
newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96);
newState.originalAvatarUrl = newState.avatarUrl;
newState.avatarFile = null;
} else if (this.state.originalAvatarUrl !== this.state.avatarUrl) {

View file

@ -26,7 +26,9 @@ import AccessibleButton from '../elements/AccessibleButton';
import QuestionDialog from '../dialogs/QuestionDialog';
import RestoreKeyBackupDialog from '../dialogs/security/RestoreKeyBackupDialog';
import { accessSecretStorage } from '../../../SecurityManager';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.settings.SecureBackupPanel")
export default class SecureBackupPanel extends React.PureComponent {
constructor(props) {
super(props);

View file

@ -27,6 +27,7 @@ import IdentityAuthClient from "../../../IdentityAuthClient";
import {abbreviateUrl, unabbreviateUrl} from "../../../utils/UrlUtils";
import { getDefaultIdentityServerUrl, doesIdentityServerHaveTerms } from '../../../utils/IdentityServerUtils';
import {timeout} from "../../../utils/promise";
import {replaceableComponent} from "../../../utils/replaceableComponent";
// We'll wait up to this long when checking for 3PID bindings on the IS.
const REACHABILITY_TIMEOUT = 10000; // ms
@ -58,6 +59,7 @@ async function checkIdentityServerUrl(u) {
}
}
@replaceableComponent("views.settings.SetIdServer")
export default class SetIdServer extends React.Component {
static propTypes = {
// Whether or not the ID server is missing terms. This affects the text

View file

@ -20,7 +20,9 @@ import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import * as sdk from '../../../index';
import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.settings.SetIntegrationManager")
export default class SetIntegrationManager extends React.Component {
constructor() {
super();

View file

@ -0,0 +1,113 @@
/*
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import SpellCheckLanguagesDropdown from "../../../components/views/elements/SpellCheckLanguagesDropdown";
import AccessibleButton from "../../../components/views/elements/AccessibleButton";
import {_t} from "../../../languageHandler";
import {replaceableComponent} from "../../../utils/replaceableComponent";
interface ExistingSpellCheckLanguageIProps {
language: string,
onRemoved(language: string),
}
interface SpellCheckLanguagesIProps {
languages: Array<string>,
onLanguagesChange(languages: Array<string>),
}
interface SpellCheckLanguagesIState {
newLanguage: string,
}
export class ExistingSpellCheckLanguage extends React.Component<ExistingSpellCheckLanguageIProps> {
_onRemove = (e) => {
e.stopPropagation();
e.preventDefault();
return this.props.onRemoved(this.props.language);
};
render() {
return (
<div className="mx_ExistingSpellCheckLanguage">
<span className="mx_ExistingSpellCheckLanguage_language">{this.props.language}</span>
<AccessibleButton onClick={this._onRemove} kind="danger_sm">
{_t("Remove")}
</AccessibleButton>
</div>
);
}
}
@replaceableComponent("views.settings.SpellCheckLanguages")
export default class SpellCheckLanguages extends React.Component<SpellCheckLanguagesIProps, SpellCheckLanguagesIState> {
constructor(props) {
super(props);
this.state = {
newLanguage: "",
}
}
_onRemoved = (language) => {
const languages = this.props.languages.filter((e) => e !== language);
this.props.onLanguagesChange(languages);
};
_onAddClick = (e) => {
e.stopPropagation();
e.preventDefault();
const language = this.state.newLanguage;
if (!language) return;
if (this.props.languages.includes(language)) return;
this.props.languages.push(language)
this.props.onLanguagesChange(this.props.languages);
};
_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} />;
});
const addButton = (
<AccessibleButton onClick={this._onAddClick} kind="primary">
{_t("Add")}
</AccessibleButton>
);
return (
<div className="mx_SpellCheckLanguages">
{existingSpellCheckLanguages}
<form onSubmit={this._onAddClick} noValidate={true}>
<SpellCheckLanguagesDropdown
className="mx_GeneralUserSettingsTab_spellCheckLanguageInput"
value={this.state.newLanguage}
onOptionChange={this._onNewLanguageChange} />
{addButton}
</form>
</div>
);
}
}

View file

@ -25,6 +25,7 @@ import * as Email from "../../../../email";
import AddThreepid from "../../../../AddThreepid";
import * as sdk from '../../../../index';
import Modal from '../../../../Modal';
import {replaceableComponent} from "../../../../utils/replaceableComponent";
/*
TODO: Improve the UX for everything in here.
@ -112,6 +113,7 @@ export class ExistingEmailAddress extends React.Component {
}
}
@replaceableComponent("views.settings.account.EmailAddresses")
export default class EmailAddresses extends React.Component {
static propTypes = {
emails: PropTypes.array.isRequired,
@ -178,19 +180,23 @@ export default class EmailAddresses extends React.Component {
e.preventDefault();
this.setState({continueDisabled: true});
this.state.addTask.checkEmailLinkClicked().then(() => {
const email = this.state.newEmailAddress;
this.state.addTask.checkEmailLinkClicked().then(([finished]) => {
let newEmailAddress = this.state.newEmailAddress;
if (finished) {
const email = this.state.newEmailAddress;
const emails = [
...this.props.emails,
{ address: email, medium: "email" },
];
this.props.onEmailsChange(emails);
newEmailAddress = "";
}
this.setState({
addTask: null,
continueDisabled: false,
verifying: false,
newEmailAddress: "",
newEmailAddress,
});
const emails = [
...this.props.emails,
{ address: email, medium: "email" },
];
this.props.onEmailsChange(emails);
}).catch((err) => {
this.setState({continueDisabled: false});
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");

View file

@ -25,6 +25,7 @@ import AddThreepid from "../../../../AddThreepid";
import CountryDropdown from "../../auth/CountryDropdown";
import * as sdk from '../../../../index';
import Modal from '../../../../Modal';
import {replaceableComponent} from "../../../../utils/replaceableComponent";
/*
TODO: Improve the UX for everything in here.
@ -107,6 +108,7 @@ export class ExistingPhoneNumber extends React.Component {
}
}
@replaceableComponent("views.settings.account.PhoneNumbers")
export default class PhoneNumbers extends React.Component {
static propTypes = {
msisdns: PropTypes.array.isRequired,
@ -177,21 +179,25 @@ export default class PhoneNumbers extends React.Component {
this.setState({continueDisabled: true});
const token = this.state.newPhoneNumberCode;
const address = this.state.verifyMsisdn;
this.state.addTask.haveMsisdnToken(token).then(() => {
this.state.addTask.haveMsisdnToken(token).then(([finished]) => {
let newPhoneNumber = this.state.newPhoneNumber;
if (finished) {
const msisdns = [
...this.props.msisdns,
{ address, medium: "msisdn" },
];
this.props.onMsisdnsChange(msisdns);
newPhoneNumber = "";
}
this.setState({
addTask: null,
continueDisabled: false,
verifying: false,
verifyMsisdn: "",
verifyError: null,
newPhoneNumber: "",
newPhoneNumber,
newPhoneNumberCode: "",
});
const msisdns = [
...this.props.msisdns,
{ address, medium: "msisdn" },
];
this.props.onMsisdnsChange(msisdns);
}).catch((err) => {
this.setState({continueDisabled: false});
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {

View file

@ -23,6 +23,7 @@ import {MatrixClientPeg} from "../../../../MatrixClientPeg";
import * as sdk from '../../../../index';
import Modal from '../../../../Modal';
import AddThreepid from '../../../../AddThreepid';
import {replaceableComponent} from "../../../../utils/replaceableComponent";
/*
TODO: Improve the UX for everything in here.
@ -202,6 +203,7 @@ export class EmailAddress extends React.Component {
className="mx_ExistingEmailAddress_confirmBtn"
kind="primary_sm"
onClick={this.onContinueClick}
disabled={this.state.continueDisabled}
>
{_t("Complete")}
</AccessibleButton>
@ -233,6 +235,7 @@ export class EmailAddress extends React.Component {
}
}
@replaceableComponent("views.settings.discovery.EmailAddresses")
export default class EmailAddresses extends React.Component {
static propTypes = {
emails: PropTypes.array.isRequired,

View file

@ -23,6 +23,7 @@ import {MatrixClientPeg} from "../../../../MatrixClientPeg";
import * as sdk from '../../../../index';
import Modal from '../../../../Modal';
import AddThreepid from '../../../../AddThreepid';
import {replaceableComponent} from "../../../../utils/replaceableComponent";
/*
TODO: Improve the UX for everything in here.
@ -246,6 +247,7 @@ export class PhoneNumber extends React.Component {
}
}
@replaceableComponent("views.settings.discovery.PhoneNumbers")
export default class PhoneNumbers extends React.Component {
static propTypes = {
msisdns: PropTypes.array.isRequired,

View file

@ -22,7 +22,9 @@ import * as sdk from "../../../../..";
import AccessibleButton from "../../../elements/AccessibleButton";
import Modal from "../../../../../Modal";
import dis from "../../../../../dispatcher/dispatcher";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
@replaceableComponent("views.settings.tabs.room.AdvancedRoomSettingsTab")
export default class AdvancedRoomSettingsTab extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,

View file

@ -21,6 +21,7 @@ import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import {_t} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import BridgeTile from "../../BridgeTile";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
const BRIDGE_EVENT_TYPES = [
"uk.half-shot.bridge",
@ -33,6 +34,7 @@ interface IProps {
roomId: string;
}
@replaceableComponent("views.settings.tabs.room.BridgeSettingsTab")
export default class BridgeSettingsTab extends React.Component<IProps> {
private renderBridgeCard(event: MatrixEvent, room: Room) {
const content = event.getContent();

View file

@ -24,7 +24,9 @@ import dis from "../../../../../dispatcher/dispatcher";
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
import SettingsStore from "../../../../../settings/SettingsStore";
import {UIFeature} from "../../../../../settings/UIFeature";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
@replaceableComponent("views.settings.tabs.room.GeneralRoomSettingsTab")
export default class GeneralRoomSettingsTab extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,

View file

@ -22,7 +22,9 @@ import AccessibleButton from "../../../elements/AccessibleButton";
import Notifier from "../../../../../Notifier";
import SettingsStore from '../../../../../settings/SettingsStore';
import {SettingLevel} from "../../../../../settings/SettingLevel";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
@replaceableComponent("views.settings.tabs.room.NotificationsSettingsTab")
export default class NotificationsSettingsTab extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,

View file

@ -21,6 +21,7 @@ import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../..";
import AccessibleButton from "../../../elements/AccessibleButton";
import Modal from "../../../../../Modal";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
const plEventsToLabels = {
// These will be translated for us later.
@ -103,6 +104,7 @@ export class BannedUser extends React.Component {
}
}
@replaceableComponent("views.settings.tabs.room.RolesRoomSettingsTab")
export default class RolesRoomSettingsTab extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,

View file

@ -26,7 +26,9 @@ import StyledRadioGroup from '../../../elements/StyledRadioGroup';
import {SettingLevel} from "../../../../../settings/SettingLevel";
import SettingsStore from "../../../../../settings/SettingsStore";
import {UIFeature} from "../../../../../settings/UIFeature";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
@replaceableComponent("views.settings.tabs.room.SecurityRoomSettingsTab")
export default class SecurityRoomSettingsTab extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,

View file

@ -37,6 +37,7 @@ import StyledRadioGroup from "../../../elements/StyledRadioGroup";
import { SettingLevel } from "../../../../../settings/SettingLevel";
import {UIFeature} from "../../../../../settings/UIFeature";
import {Layout} from "../../../../../settings/Layout";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
interface IProps {
}
@ -69,7 +70,7 @@ interface IState extends IThemeState {
avatarUrl: string;
}
@replaceableComponent("views.settings.tabs.user.AppearanceUserSettingsTab")
export default class AppearanceUserSettingsTab extends React.Component<IProps, IState> {
private readonly MESSAGE_PREVIEW_TEXT = _t("Hey you. You're the best!");

View file

@ -17,7 +17,9 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
import GroupUserSettings from "../../../groups/GroupUserSettings";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
@replaceableComponent("views.settings.tabs.user.FlairUserSettingsTab")
export default class FlairUserSettingsTab extends React.Component {
render() {
return (

View file

@ -22,6 +22,7 @@ import ProfileSettings from "../../ProfileSettings";
import * as languageHandler from "../../../../../languageHandler";
import SettingsStore from "../../../../../settings/SettingsStore";
import LanguageDropdown from "../../../elements/LanguageDropdown";
import SpellCheckSettings from "../../SpellCheckSettings";
import AccessibleButton from "../../../elements/AccessibleButton";
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
import PropTypes from "prop-types";
@ -31,14 +32,16 @@ import * as sdk from "../../../../..";
import Modal from "../../../../../Modal";
import dis from "../../../../../dispatcher/dispatcher";
import {Service, startTermsFlow} from "../../../../../Terms";
import {SERVICE_TYPES} from "matrix-js-sdk";
import {SERVICE_TYPES} from "matrix-js-sdk/src/service-types";
import IdentityAuthClient from "../../../../../IdentityAuthClient";
import {abbreviateUrl} from "../../../../../utils/UrlUtils";
import { getThreepidsWithBindStatus } from '../../../../../boundThreepids';
import Spinner from "../../../elements/Spinner";
import {SettingLevel} from "../../../../../settings/SettingLevel";
import {UIFeature} from "../../../../../settings/UIFeature";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
@replaceableComponent("views.settings.tabs.user.GeneralUserSettingsTab")
export default class GeneralUserSettingsTab extends React.Component {
static propTypes = {
closeSettingsFn: PropTypes.func.isRequired,
@ -49,6 +52,7 @@ export default class GeneralUserSettingsTab extends React.Component {
this.state = {
language: languageHandler.getCurrentLanguage(),
spellCheckLanguages: [],
haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()),
serverSupportsSeparateAddAndBind: null,
idServerHasUnsignedTerms: false,
@ -85,6 +89,15 @@ export default class GeneralUserSettingsTab extends React.Component {
this._getThreepidState();
}
async componentDidMount() {
const plaf = PlatformPeg.get();
if (plaf) {
this.setState({
spellCheckLanguages: await plaf.getSpellCheckLanguages(),
});
}
}
componentWillUnmount() {
dis.unregister(this.dispatcherRef);
}
@ -182,12 +195,21 @@ export default class GeneralUserSettingsTab extends React.Component {
PlatformPeg.get().reload();
};
_onSpellCheckLanguagesChange = (languages) => {
this.setState({spellCheckLanguages: languages});
const plaf = PlatformPeg.get();
if (plaf) {
plaf.setSpellCheckLanguages(languages);
}
};
_onPasswordChangeError = (err) => {
// TODO: Figure out a design that doesn't involve replacing the current dialog
let errMsg = err.error || "";
let errMsg = err.error || err.message || "";
if (err.httpStatus === 403) {
errMsg = _t("Failed to change password. Is your password correct?");
} else if (err.httpStatus) {
} else if (!errMsg) {
errMsg += ` (HTTP status ${err.httpStatus})`;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -303,6 +325,16 @@ export default class GeneralUserSettingsTab extends React.Component {
);
}
_renderSpellCheckSection() {
return (
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Spell check dictionaries")}</span>
<SpellCheckSettings languages={this.state.spellCheckLanguages}
onLanguagesChange={this._onSpellCheckLanguagesChange} />
</div>
);
}
_renderDiscoverySection() {
const SetIdServer = sdk.getComponent("views.settings.SetIdServer");
@ -381,6 +413,9 @@ export default class GeneralUserSettingsTab extends React.Component {
}
render() {
const plaf = PlatformPeg.get();
const supportsMultiLanguageSpellCheck = plaf.supportsMultiLanguageSpellCheck();
const discoWarning = this.state.requiredPolicyInfo.hasTerms
? <img className='mx_GeneralUserSettingsTab_warningIcon'
src={require("../../../../../../res/img/feather-customised/warning-triangle.svg")}
@ -409,6 +444,7 @@ export default class GeneralUserSettingsTab extends React.Component {
{this._renderProfileSection()}
{this._renderAccountSection()}
{this._renderLanguageSection()}
{supportsMultiLanguageSpellCheck ? this._renderSpellCheckSection() : null}
{ discoverySection }
{this._renderIntegrationManagerSection() /* Has its own title */}
{ accountManagementSection }

View file

@ -27,7 +27,9 @@ import * as sdk from "../../../../../";
import PlatformPeg from "../../../../../PlatformPeg";
import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts";
import UpdateCheckButton from "../../UpdateCheckButton";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
@replaceableComponent("views.settings.tabs.user.HelpUserSettingsTab")
export default class HelpUserSettingsTab extends React.Component {
static propTypes = {
closeSettingsFn: PropTypes.func.isRequired,

View file

@ -21,6 +21,7 @@ import SettingsStore from "../../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import * as sdk from "../../../../../index";
import {SettingLevel} from "../../../../../settings/SettingLevel";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
export class LabsSettingToggle extends React.Component {
static propTypes = {
@ -40,6 +41,7 @@ export class LabsSettingToggle extends React.Component {
}
}
@replaceableComponent("views.settings.tabs.user.LabsUserSettingsTab")
export default class LabsUserSettingsTab extends React.Component {
constructor() {
super();

View file

@ -23,7 +23,9 @@ import {BanList, RULE_SERVER, RULE_USER} from "../../../../../mjolnir/BanList";
import Modal from "../../../../../Modal";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../../index";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
@replaceableComponent("views.settings.tabs.user.MjolnirUserSettingsTab")
export default class MjolnirUserSettingsTab extends React.Component {
constructor() {
super();

View file

@ -17,7 +17,9 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
import * as sdk from "../../../../../index";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
@replaceableComponent("views.settings.tabs.user.NotificationUserSettingsTab")
export default class NotificationUserSettingsTab extends React.Component {
constructor() {
super();

View file

@ -23,7 +23,9 @@ import Field from "../../../elements/Field";
import * as sdk from "../../../../..";
import PlatformPeg from "../../../../../PlatformPeg";
import {SettingLevel} from "../../../../../settings/SettingLevel";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
@replaceableComponent("views.settings.tabs.user.PreferencesUserSettingsTab")
export default class PreferencesUserSettingsTab extends React.Component {
static ROOM_LIST_SETTINGS = [
'breadcrumbs',
@ -48,6 +50,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
'showRedactions',
'enableSyntaxHighlightLanguageDetection',
'expandCodeByDefault',
'scrollToBottomOnMessageSent',
'showCodeLineNumbers',
'showJoinLeaves',
'showAvatarChanges',
@ -71,6 +74,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
this.state = {
autoLaunch: false,
autoLaunchSupported: false,
warnBeforeExit: true,
warnBeforeExitSupported: false,
alwaysShowMenuBar: true,
alwaysShowMenuBarSupported: false,
minimizeToTray: true,
@ -93,6 +98,12 @@ export default class PreferencesUserSettingsTab extends React.Component {
autoLaunch = await platform.getAutoLaunchEnabled();
}
const warnBeforeExitSupported = await platform.supportsWarnBeforeExit();
let warnBeforeExit = false;
if (warnBeforeExitSupported) {
warnBeforeExit = await platform.shouldWarnBeforeExit();
}
const alwaysShowMenuBarSupported = await platform.supportsAutoHideMenuBar();
let alwaysShowMenuBar = true;
if (alwaysShowMenuBarSupported) {
@ -108,6 +119,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
this.setState({
autoLaunch,
autoLaunchSupported,
warnBeforeExit,
warnBeforeExitSupported,
alwaysShowMenuBarSupported,
alwaysShowMenuBar,
minimizeToTraySupported,
@ -119,6 +132,10 @@ export default class PreferencesUserSettingsTab extends React.Component {
PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({autoLaunch: checked}));
};
_onWarnBeforeExitChange = (checked) => {
PlatformPeg.get().setWarnBeforeExit(checked).then(() => this.setState({warnBeforeExit: checked}));
}
_onAlwaysShowMenuBarChange = (checked) => {
PlatformPeg.get().setAutoHideMenuBarEnabled(!checked).then(() => this.setState({alwaysShowMenuBar: checked}));
};
@ -158,6 +175,14 @@ export default class PreferencesUserSettingsTab extends React.Component {
label={_t('Start automatically after system login')} />;
}
let warnBeforeExitOption = null;
if (this.state.warnBeforeExitSupported) {
warnBeforeExitOption = <LabelledToggleSwitch
value={this.state.warnBeforeExit}
onChange={this._onWarnBeforeExitChange}
label={_t('Warn before quitting')} />;
}
let autoHideMenuOption = null;
if (this.state.alwaysShowMenuBarSupported) {
autoHideMenuOption = <LabelledToggleSwitch
@ -199,6 +224,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
{minimizeToTrayOption}
{autoHideMenuOption}
{autoLaunchOption}
{warnBeforeExitOption}
<Field
label={_t('Autocomplete delay (ms)')}
type='number'

View file

@ -34,6 +34,7 @@ import SettingsStore from "../../../../../settings/SettingsStore";
import {UIFeature} from "../../../../../settings/UIFeature";
import {isE2eAdvancedPanelPossible} from "../../E2eAdvancedPanel";
import CountlyAnalytics from "../../../../../CountlyAnalytics";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
export class IgnoredUser extends React.Component {
static propTypes = {
@ -59,6 +60,7 @@ export class IgnoredUser extends React.Component {
}
}
@replaceableComponent("views.settings.tabs.user.SecurityUserSettingsTab")
export default class SecurityUserSettingsTab extends React.Component {
static propTypes = {
closeSettingsFn: PropTypes.func.isRequired,

View file

@ -25,7 +25,9 @@ import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../../index";
import Modal from "../../../../../Modal";
import {SettingLevel} from "../../../../../settings/SettingLevel";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
@replaceableComponent("views.settings.tabs.user.VoiceUserSettingsTab")
export default class VoiceUserSettingsTab extends React.Component {
constructor() {
super();
@ -82,6 +84,7 @@ export default class VoiceUserSettingsTab extends React.Component {
}
}
if (error) {
console.log("Failed to list userMedia devices", error);
const brand = SdkConfig.get().brand;
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
Modal.createTrackedDialog('No media permissions', '', ErrorDialog, {