diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles
index e7f6ee1f84..c129f801a1 100644
--- a/.eslintignore.errorfiles
+++ b/.eslintignore.errorfiles
@@ -50,7 +50,6 @@ src/components/views/settings/Notifications.js
src/GroupAddressPicker.js
src/HtmlUtils.js
src/ImageUtils.js
-src/languageHandler.js
src/linkify-matrix.js
src/Markdown.js
src/MatrixClientPeg.js
diff --git a/.stylelintrc.js b/.stylelintrc.js
index 97e1ec8023..f028c76cc0 100644
--- a/.stylelintrc.js
+++ b/.stylelintrc.js
@@ -15,6 +15,9 @@ module.exports = {
"number-leading-zero": null,
"selector-list-comma-newline-after": null,
"at-rule-no-unknown": null,
- "scss/at-rule-no-unknown": true,
+ "scss/at-rule-no-unknown": [true, {
+ // https://github.com/vector-im/riot-web/issues/10544
+ "ignoreAtRules": ["define-mixin"],
+ }],
}
}
diff --git a/res/css/_common.scss b/res/css/_common.scss
index 517ced43fb..1b7c8ec938 100644
--- a/res/css/_common.scss
+++ b/res/css/_common.scss
@@ -559,3 +559,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
.mx_Username_color8 {
color: $username-variant8-color;
}
+
+@define-mixin mx_Settings_fullWidthField {
+ margin-right: 200px;
+}
diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss
index 3e97a0ff6d..afac75986f 100644
--- a/res/css/views/settings/_ProfileSettings.scss
+++ b/res/css/views/settings/_ProfileSettings.scss
@@ -26,6 +26,10 @@ limitations under the License.
height: 4em;
}
+.mx_ProfileSettings_controls .mx_Field {
+ margin-right: 100px;
+}
+
.mx_ProfileSettings_controls .mx_Field:first-child {
margin-top: 0;
}
diff --git a/res/css/views/settings/_SetIdServer.scss b/res/css/views/settings/_SetIdServer.scss
index c6fcfc8af5..55ad6eef02 100644
--- a/res/css/views/settings/_SetIdServer.scss
+++ b/res/css/views/settings/_SetIdServer.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,5 +15,5 @@ limitations under the License.
*/
.mx_SetIdServer .mx_Field_input {
- width: 300px;
+ @mixin mx_Settings_fullWidthField;
}
diff --git a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss
index 3b330f2c30..16467165cf 100644
--- a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss
+++ b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss
@@ -16,7 +16,7 @@ limitations under the License.
.mx_GeneralUserSettingsTab_changePassword .mx_Field,
.mx_GeneralUserSettingsTab_themeSection .mx_Field {
- margin-right: 100px; // Align with the other fields on the page
+ @mixin mx_Settings_fullWidthField;
}
.mx_GeneralUserSettingsTab_changePassword .mx_Field:first-child {
@@ -26,5 +26,5 @@ limitations under the License.
.mx_GeneralUserSettingsTab_accountSection .mx_EmailAddresses,
.mx_GeneralUserSettingsTab_accountSection .mx_PhoneNumbers,
.mx_GeneralUserSettingsTab_languageInput {
- margin-right: 100px; // Align with the other fields on the page
+ @mixin mx_Settings_fullWidthField;
}
diff --git a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss
index b3430f47af..d003e175d9 100644
--- a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss
+++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss
@@ -15,5 +15,5 @@ limitations under the License.
*/
.mx_PreferencesUserSettingsTab .mx_Field {
- margin-right: 100px; // Align with the rest of the controls
+ @mixin mx_Settings_fullWidthField;
}
diff --git a/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss
index 36c8cfd896..69d57bdba1 100644
--- a/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss
+++ b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss
@@ -15,7 +15,7 @@ limitations under the License.
*/
.mx_VoiceUserSettingsTab .mx_Field {
- margin-right: 100px; // align with the rest of the fields
+ @mixin mx_Settings_fullWidthField;
}
.mx_VoiceUserSettingsTab_missingMediaPermissions {
diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js
index 084ec1bd6a..8272b36639 100644
--- a/src/components/views/elements/Field.js
+++ b/src/components/views/elements/Field.js
@@ -48,7 +48,7 @@ export default class Field extends React.PureComponent {
onValidate: PropTypes.func,
// If specified, contents will appear as a tooltip on the element and
// validation feedback tooltips will be suppressed.
- tooltip: PropTypes.node,
+ tooltipContent: PropTypes.node,
// All other props pass through to the .
};
@@ -137,8 +137,7 @@ export default class Field extends React.PureComponent {
}, VALIDATION_THROTTLE_MS);
render() {
- const { element, prefix, onValidate, children, ...inputProps } = this.props;
- delete inputProps.tooltip; // needs to be removed from props but we don't need it here
+ const { element, prefix, onValidate, children, tooltipContent, ...inputProps } = this.props;
const inputElement = element || "input";
@@ -170,11 +169,11 @@ export default class Field extends React.PureComponent {
// Handle displaying feedback on validity
const Tooltip = sdk.getComponent("elements.Tooltip");
let fieldTooltip;
- if (this.props.tooltip || this.state.feedback) {
+ if (tooltipContent || this.state.feedback) {
fieldTooltip = ;
}
diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js
index a87fe034a1..398e578e8d 100644
--- a/src/components/views/settings/SetIdServer.js
+++ b/src/components/views/settings/SetIdServer.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import request from 'browser-request';
import url from 'url';
import React from 'react';
import {_t} from "../../../languageHandler";
import sdk from '../../../index';
import MatrixClientPeg from "../../../MatrixClientPeg";
import SdkConfig from "../../../SdkConfig";
-import Field from "../elements/Field";
+import Modal from '../../../Modal';
+import dis from "../../../dispatcher";
/**
* If a url has no path component, etc. abbreviate it to just the hostname
@@ -58,41 +58,39 @@ function unabbreviateUrl(u) {
/**
* Check an IS URL is valid, including liveness check
*
- * @param {string} isUrl The url to check
+ * @param {string} u The url to check
* @returns {string} null if url passes all checks, otherwise i18ned error string
*/
-async function checkIsUrl(isUrl) {
- const parsedUrl = url.parse(isUrl);
+async function checkIdentityServerUrl(u) {
+ const parsedUrl = url.parse(u);
if (parsedUrl.protocol !== 'https:') return _t("Identity Server URL must be HTTPS");
// XXX: duplicated logic from js-sdk but it's quite tied up in the validation logic in the
// js-sdk so probably as easy to duplicate it than to separate it out so we can reuse it
- return new Promise((resolve) => {
- request(
- // also XXX: we don't really know whether to hit /v1 or /v2 for this: we
- // probably want a /versions endpoint like the C/S API.
- { method: "GET", url: isUrl + '/_matrix/identity/api/v1' },
- (err, response, body) => {
- if (err) {
- resolve(_t("Could not connect to ID Server"));
- } else if (response.status < 200 || response.status >= 300) {
- resolve(_t("Not a valid ID Server (status code %(code)s)", {code: response.status}));
- } else {
- resolve(null);
- }
- },
- );
- });
+ try {
+ const response = await fetch(u + '/_matrix/identity/api/v1');
+ if (response.ok) {
+ return null;
+ } else if (response.status < 200 || response.status >= 300) {
+ return _t("Not a valid Identity Server (status code %(code)s)", {code: response.status});
+ } else {
+ return _t("Could not connect to Identity Server");
+ }
+ } catch (e) {
+ return _t("Could not connect to Identity Server");
+ }
}
export default class SetIdServer extends React.Component {
constructor() {
super();
- let defaultIdServer = abbreviateUrl(MatrixClientPeg.get().getIdentityServerUrl());
- if (!defaultIdServer) {
- defaultIdServer = abbreviateUrl(SdkConfig.get()['validated_server_config']['idServer']) || '';
+ let defaultIdServer = '';
+ if (!MatrixClientPeg.get().getIdentityServerUrl() && SdkConfig.get()['validated_server_config']['isUrl']) {
+ // If no ID server is configured but there's one in the config, prepopulate
+ // the field to help the user.
+ defaultIdServer = abbreviateUrl(SdkConfig.get()['validated_server_config']['isUrl']);
}
this.state = {
@@ -114,7 +112,7 @@ export default class SetIdServer extends React.Component {
const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner');
return
+ {_t(
+ "Disconnect from the identity server ?", {},
+ {idserver: sub => {abbreviateUrl(this.state.currentClientIdServer)}},
+ )},
+
,
+ button: _t("Disconnect"),
+ onFinished: (confirmed) => {
+ if (confirmed) {
+ this._disconnectIdServer();
+ }
+ },
+ });
+ };
+
+ _disconnectIdServer = () => {
+ MatrixClientPeg.get().setIdentityServerUrl(null);
+ localStorage.removeItem("mx_is_access_token");
+ localStorage.removeItem("mx_is_url");
+
+ let newFieldVal = '';
+ if (SdkConfig.get()['validated_server_config']['isUrl']) {
+ // Prepopulate the client's default so the user at least has some idea of
+ // a valid value they might enter
+ newFieldVal = abbreviateUrl(SdkConfig.get()['validated_server_config']['isUrl']);
+ }
+
+ this.setState({
+ busy: false,
+ error: null,
+ currentClientIdServer: MatrixClientPeg.get().getIdentityServerUrl(),
+ idServer: newFieldVal,
+ });
+ };
+
render() {
+ const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
+ const Field = sdk.getComponent('elements.Field');
const idServerUrl = this.state.currentClientIdServer;
let sectionTitle;
let bodyText;
@@ -164,12 +207,26 @@ export default class SetIdServer extends React.Component {
} else {
sectionTitle = _t("Identity Server");
bodyText = _t(
- "You are not currently using an Identity Server. " +
+ "You are not currently using an identity server. " +
"To discover and be discoverable by existing contacts you know, " +
- "add one below",
+ "add one below.",
);
}
+ let discoSection;
+ if (idServerUrl) {
+ discoSection =
+ {_t(
+ "Disconnecting from your identity server will mean you " +
+ "won't be discoverable by other users and you won't be " +
+ "able to invite others by email or phone.",
+ )}
+
+ {_t("Disconnect")}
+
+
{_t("Email addresses")}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index a925010f6e..e31a604fe3 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -540,13 +540,17 @@
"Display Name": "Display Name",
"Save": "Save",
"Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS",
- "Could not connect to ID Server": "Could not connect to ID Server",
- "Not a valid ID Server (status code %(code)s)": "Not a valid ID Server (status code %(code)s)",
- "Checking Server": "Checking Server",
+ "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)",
+ "Could not connect to Identity Server": "Could not connect to Identity Server",
+ "Checking server": "Checking server",
+ "Disconnect Identity Server": "Disconnect Identity Server",
+ "Disconnect from the identity server ?": "Disconnect from the identity server ?",
+ "Disconnect": "Disconnect",
"Identity Server (%(server)s)": "Identity Server (%(server)s)",
"You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.",
"Identity Server": "Identity Server",
- "You are not currently using an Identity Server. To discover and be discoverable by existing contacts you know, add one below": "You are not currently using an Identity Server. To discover and be discoverable by existing contacts you know, add one below",
+ "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.",
+ "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.",
"Change": "Change",
"Checking server": "Checking server",
"Integration manager offline or not accessible.": "Integration manager offline or not accessible.",
diff --git a/src/languageHandler.js b/src/languageHandler.js
index c1a426383b..474cd2b3cd 100644
--- a/src/languageHandler.js
+++ b/src/languageHandler.js
@@ -2,6 +2,7 @@
Copyright 2017 MTRNord and Cooperative EITA
Copyright 2017 Vector Creations Ltd.
Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -102,7 +103,7 @@ function safeCounterpartTranslate(text, options) {
* @return a React component if any non-strings were used in substitutions, otherwise a string
*/
export function _t(text, variables, tags) {
- // Don't do subsitutions in counterpart. We handle it ourselves so we can replace with React components
+ // Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components
// However, still pass the variables to counterpart so that it can choose the correct plural if count is given
// It is enough to pass the count variable, but in the future counterpart might make use of other information too
const args = Object.assign({ interpolate: false }, variables);
@@ -289,7 +290,7 @@ export function setLanguage(preferredLangs) {
console.log("set language to " + langToUse);
// Set 'en' as fallback language:
- if (langToUse != "en") {
+ if (langToUse !== "en") {
return getLanguage(i18nFolder + availLangs['en'].fileName);
}
}).then((langData) => {
@@ -329,13 +330,13 @@ export function getLanguagesFromBrowser() {
*/
export function getNormalizedLanguageKeys(language) {
const languageKeys = [];
- const normalizedLanguage = this.normalizeLanguageKey(language);
+ const normalizedLanguage = normalizeLanguageKey(language);
const languageParts = normalizedLanguage.split('-');
- if (languageParts.length == 2 && languageParts[0] == languageParts[1]) {
+ if (languageParts.length === 2 && languageParts[0] === languageParts[1]) {
languageKeys.push(languageParts[0]);
} else {
languageKeys.push(normalizedLanguage);
- if (languageParts.length == 2) {
+ if (languageParts.length === 2) {
languageKeys.push(languageParts[0]);
}
}
@@ -345,6 +346,9 @@ export function getNormalizedLanguageKeys(language) {
/**
* Returns a language string with underscores replaced with
* hyphens, and lowercased.
+ *
+ * @param {string} language The language string to be normalized
+ * @returns {string} The normalized language string
*/
export function normalizeLanguageKey(language) {
return language.toLowerCase().replace("_", "-");
@@ -373,8 +377,8 @@ export function pickBestLanguage(langs) {
}
{
- // Failing that, a different dialect of the same lnguage
- const closeLangIndex = normalisedLangs.find((l) => l.substr(0,2) === currentLang.substr(0,2));
+ // Failing that, a different dialect of the same language
+ const closeLangIndex = normalisedLangs.find((l) => l.substr(0, 2) === currentLang.substr(0, 2));
if (closeLangIndex > -1) return langs[closeLangIndex];
}