mirror of
https://github.com/element-hq/element-web
synced 2024-11-28 12:28:50 +03:00
Merge branch 'develop' into travis/granular-settings
This commit is contained in:
commit
0648b3eae7
14 changed files with 481 additions and 248 deletions
|
@ -204,6 +204,12 @@ export default class Login {
|
|||
}
|
||||
throw originalLoginError;
|
||||
}).catch((error) => {
|
||||
// We apparently squash case at login serverside these days:
|
||||
// https://github.com/matrix-org/synapse/blob/1189be43a2479f5adf034613e8d10e3f4f452eb9/synapse/handlers/auth.py#L475
|
||||
// so this wasn't needed after all. Keeping the code around in case the
|
||||
// the situation changes...
|
||||
|
||||
/*
|
||||
if (
|
||||
error.httpStatus === 403 &&
|
||||
loginParams.identifier.type === 'm.id.user' &&
|
||||
|
@ -211,6 +217,7 @@ export default class Login {
|
|||
) {
|
||||
return tryLowercaseUsername(originalLoginError);
|
||||
}
|
||||
*/
|
||||
throw originalLoginError;
|
||||
}).catch((error) => {
|
||||
console.log("Login failed", error);
|
||||
|
|
|
@ -26,7 +26,7 @@ const DEFAULTS = {
|
|||
class SdkConfig {
|
||||
|
||||
static get() {
|
||||
return global.mxReactSdkConfig;
|
||||
return global.mxReactSdkConfig || {};
|
||||
}
|
||||
|
||||
static put(cfg) {
|
||||
|
|
365
src/Tinter.js
365
src/Tinter.js
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -14,125 +15,9 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// FIXME: these vars should be bundled up and attached to
|
||||
// module.exports otherwise this will break when included by both
|
||||
// react-sdk and apps layered on top.
|
||||
|
||||
const DEBUG = 0;
|
||||
|
||||
// The colour keys to be replaced as referred to in CSS
|
||||
const keyRgb = [
|
||||
"rgb(118, 207, 166)", // Vector Green
|
||||
"rgb(234, 245, 240)", // Vector Light Green
|
||||
"rgb(211, 239, 225)", // BottomLeftMenu overlay (20% Vector Green)
|
||||
];
|
||||
|
||||
// Some algebra workings for calculating the tint % of Vector Green & Light Green
|
||||
// x * 118 + (1 - x) * 255 = 234
|
||||
// x * 118 + 255 - 255 * x = 234
|
||||
// x * 118 - x * 255 = 234 - 255
|
||||
// (255 - 118) x = 255 - 234
|
||||
// x = (255 - 234) / (255 - 118) = 0.16
|
||||
|
||||
// The colour keys to be replaced as referred to in SVGs
|
||||
const keyHex = [
|
||||
"#76CFA6", // Vector Green
|
||||
"#EAF5F0", // Vector Light Green
|
||||
"#D3EFE1", // BottomLeftMenu overlay (20% Vector Green overlaid on Vector Light Green)
|
||||
"#FFFFFF", // white highlights of the SVGs (for switching to dark theme)
|
||||
];
|
||||
|
||||
// cache of our replacement colours
|
||||
// defaults to our keys.
|
||||
const colors = [
|
||||
keyHex[0],
|
||||
keyHex[1],
|
||||
keyHex[2],
|
||||
keyHex[3],
|
||||
];
|
||||
|
||||
const cssFixups = [
|
||||
// {
|
||||
// style: a style object that should be fixed up taken from a stylesheet
|
||||
// attr: name of the attribute to be clobbered, e.g. 'color'
|
||||
// index: ordinal of primary, secondary or tertiary
|
||||
// }
|
||||
];
|
||||
|
||||
// CSS attributes to be fixed up
|
||||
const cssAttrs = [
|
||||
"color",
|
||||
"backgroundColor",
|
||||
"borderColor",
|
||||
"borderTopColor",
|
||||
"borderBottomColor",
|
||||
"borderLeftColor",
|
||||
];
|
||||
|
||||
const svgAttrs = [
|
||||
"fill",
|
||||
"stroke",
|
||||
];
|
||||
|
||||
let cached = false;
|
||||
|
||||
function calcCssFixups() {
|
||||
if (DEBUG) console.log("calcSvgFixups start");
|
||||
for (let i = 0; i < document.styleSheets.length; i++) {
|
||||
const ss = document.styleSheets[i];
|
||||
if (!ss) continue; // well done safari >:(
|
||||
// Chromium apparently sometimes returns null here; unsure why.
|
||||
// see $14534907369972FRXBx:matrix.org in HQ
|
||||
// ...ah, it's because there's a third party extension like
|
||||
// privacybadger inserting its own stylesheet in there with a
|
||||
// resource:// URI or something which results in a XSS error.
|
||||
// See also #vector:matrix.org/$145357669685386ebCfr:matrix.org
|
||||
// ...except some browsers apparently return stylesheets without
|
||||
// hrefs, which we have no choice but ignore right now
|
||||
|
||||
// XXX seriously? we are hardcoding the name of vector's CSS file in
|
||||
// here?
|
||||
//
|
||||
// Why do we need to limit it to vector's CSS file anyway - if there
|
||||
// are other CSS files affecting the doc don't we want to apply the
|
||||
// same transformations to them?
|
||||
//
|
||||
// Iterating through the CSS looking for matches to hack on feels
|
||||
// pretty horrible anyway. And what if the application skin doesn't use
|
||||
// Vector Green as its primary color?
|
||||
|
||||
if (ss.href && !ss.href.match(/\/bundle.*\.css$/)) continue;
|
||||
|
||||
if (!ss.cssRules) continue;
|
||||
for (let j = 0; j < ss.cssRules.length; j++) {
|
||||
const rule = ss.cssRules[j];
|
||||
if (!rule.style) continue;
|
||||
for (let k = 0; k < cssAttrs.length; k++) {
|
||||
const attr = cssAttrs[k];
|
||||
for (let l = 0; l < keyRgb.length; l++) {
|
||||
if (rule.style[attr] === keyRgb[l]) {
|
||||
cssFixups.push({
|
||||
style: rule.style,
|
||||
attr: attr,
|
||||
index: l,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DEBUG) console.log("calcSvgFixups end");
|
||||
}
|
||||
|
||||
function applyCssFixups() {
|
||||
if (DEBUG) console.log("applyCssFixups start");
|
||||
for (let i = 0; i < cssFixups.length; i++) {
|
||||
const cssFixup = cssFixups[i];
|
||||
cssFixup.style[cssFixup.attr] = colors[cssFixup.index];
|
||||
}
|
||||
if (DEBUG) console.log("applyCssFixups end");
|
||||
}
|
||||
|
||||
// utility to turn #rrggbb into [red,green,blue]
|
||||
function hexToRgb(color) {
|
||||
if (color[0] === '#') color = color.slice(1);
|
||||
if (color.length === 3) {
|
||||
|
@ -147,15 +32,77 @@ function hexToRgb(color) {
|
|||
return [r, g, b];
|
||||
}
|
||||
|
||||
// utility to turn [red,green,blue] into #rrggbb
|
||||
function rgbToHex(rgb) {
|
||||
const val = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
|
||||
return '#' + (0x1000000 + val).toString(16).slice(1);
|
||||
}
|
||||
|
||||
// List of functions to call when the tint changes.
|
||||
const tintables = [];
|
||||
class Tinter {
|
||||
constructor() {
|
||||
// The default colour keys to be replaced as referred to in CSS
|
||||
// (should be overridden by .mx_theme_accentColor and .mx_theme_secondaryAccentColor)
|
||||
this.keyRgb = [
|
||||
"rgb(118, 207, 166)", // Vector Green
|
||||
"rgb(234, 245, 240)", // Vector Light Green
|
||||
"rgb(211, 239, 225)", // Unused: BottomLeftMenu (20% Green overlaid on Light Green)
|
||||
];
|
||||
|
||||
// Some algebra workings for calculating the tint % of Vector Green & Light Green
|
||||
// x * 118 + (1 - x) * 255 = 234
|
||||
// x * 118 + 255 - 255 * x = 234
|
||||
// x * 118 - x * 255 = 234 - 255
|
||||
// (255 - 118) x = 255 - 234
|
||||
// x = (255 - 234) / (255 - 118) = 0.16
|
||||
|
||||
// The colour keys to be replaced as referred to in SVGs
|
||||
this.keyHex = [
|
||||
"#76CFA6", // Vector Green
|
||||
"#EAF5F0", // Vector Light Green
|
||||
"#D3EFE1", // Unused: BottomLeftMenu (20% Green overlaid on Light Green)
|
||||
"#FFFFFF", // white highlights of the SVGs (for switching to dark theme)
|
||||
];
|
||||
|
||||
// cache of our replacement colours
|
||||
// defaults to our keys.
|
||||
this.colors = [
|
||||
this.keyHex[0],
|
||||
this.keyHex[1],
|
||||
this.keyHex[2],
|
||||
this.keyHex[3],
|
||||
];
|
||||
|
||||
this.cssFixups = [
|
||||
// { theme: {
|
||||
// style: a style object that should be fixed up taken from a stylesheet
|
||||
// attr: name of the attribute to be clobbered, e.g. 'color'
|
||||
// index: ordinal of primary, secondary or tertiary
|
||||
// },
|
||||
// }
|
||||
];
|
||||
|
||||
// CSS attributes to be fixed up
|
||||
this.cssAttrs = [
|
||||
"color",
|
||||
"backgroundColor",
|
||||
"borderColor",
|
||||
"borderTopColor",
|
||||
"borderBottomColor",
|
||||
"borderLeftColor",
|
||||
];
|
||||
|
||||
this.svgAttrs = [
|
||||
"fill",
|
||||
"stroke",
|
||||
];
|
||||
|
||||
// List of functions to call when the tint changes.
|
||||
this.tintables = [];
|
||||
|
||||
// the currently loaded theme (if any)
|
||||
this.theme = undefined;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Register a callback to fire when the tint changes.
|
||||
* This is used to rewrite the tintable SVGs with the new tint.
|
||||
|
@ -167,19 +114,24 @@ module.exports = {
|
|||
*
|
||||
* @param {Function} tintable Function to call when the tint changes.
|
||||
*/
|
||||
registerTintable: function(tintable) {
|
||||
tintables.push(tintable);
|
||||
},
|
||||
registerTintable(tintable) {
|
||||
this.tintables.push(tintable);
|
||||
}
|
||||
|
||||
tint: function(primaryColor, secondaryColor, tertiaryColor) {
|
||||
if (!cached) {
|
||||
calcCssFixups();
|
||||
cached = true;
|
||||
}
|
||||
getKeyRgb() {
|
||||
return this.keyRgb;
|
||||
}
|
||||
|
||||
getCurrentColors() {
|
||||
return this.colors;
|
||||
}
|
||||
|
||||
tint(primaryColor, secondaryColor, tertiaryColor) {
|
||||
this.calcCssFixups();
|
||||
|
||||
if (!primaryColor) {
|
||||
primaryColor = "#76CFA6"; // Vector green
|
||||
secondaryColor = "#EAF5F0"; // Vector light green
|
||||
primaryColor = this.keyRgb[0];
|
||||
secondaryColor = this.keyRgb[1];
|
||||
}
|
||||
|
||||
if (!secondaryColor) {
|
||||
|
@ -201,45 +153,141 @@ module.exports = {
|
|||
tertiaryColor = rgbToHex(rgb1);
|
||||
}
|
||||
|
||||
if (colors[0] === primaryColor &&
|
||||
colors[1] === secondaryColor &&
|
||||
colors[2] === tertiaryColor) {
|
||||
if (this.colors[0] === primaryColor &&
|
||||
this.colors[1] === secondaryColor &&
|
||||
this.colors[2] === tertiaryColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
colors[0] = primaryColor;
|
||||
colors[1] = secondaryColor;
|
||||
colors[2] = tertiaryColor;
|
||||
this.colors[0] = primaryColor;
|
||||
this.colors[1] = secondaryColor;
|
||||
this.colors[2] = tertiaryColor;
|
||||
|
||||
if (DEBUG) console.log("Tinter.tint");
|
||||
|
||||
// go through manually fixing up the stylesheets.
|
||||
applyCssFixups();
|
||||
this.applyCssFixups();
|
||||
|
||||
// tell all the SVGs to go fix themselves up
|
||||
// we don't do this as a dispatch otherwise it will visually lag
|
||||
tintables.forEach(function(tintable) {
|
||||
this.tintables.forEach(function(tintable) {
|
||||
tintable();
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
tintSvgWhite: function(whiteColor) {
|
||||
tintSvgWhite(whiteColor) {
|
||||
if (!whiteColor) {
|
||||
whiteColor = colors[3];
|
||||
whiteColor = this.colors[3];
|
||||
}
|
||||
if (colors[3] === whiteColor) {
|
||||
if (this.colors[3] === whiteColor) {
|
||||
return;
|
||||
}
|
||||
colors[3] = whiteColor;
|
||||
tintables.forEach(function(tintable) {
|
||||
this.colors[3] = whiteColor;
|
||||
this.tintables.forEach(function(tintable) {
|
||||
tintable();
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
setTheme(theme) {
|
||||
this.theme = theme;
|
||||
|
||||
// update keyRgb from the current theme CSS itself, if it defines it
|
||||
if (document.getElementById('mx_theme_accentColor')) {
|
||||
this.keyRgb[0] = window.getComputedStyle(
|
||||
document.getElementById('mx_theme_accentColor')
|
||||
).color;
|
||||
}
|
||||
if (document.getElementById('mx_theme_secondaryAccentColor')) {
|
||||
this.keyRgb[1] = window.getComputedStyle(
|
||||
document.getElementById('mx_theme_secondaryAccentColor')
|
||||
).color;
|
||||
}
|
||||
|
||||
this.calcCssFixups();
|
||||
}
|
||||
|
||||
calcCssFixups() {
|
||||
// cache our fixups
|
||||
if (this.cssFixups[this.theme]) return;
|
||||
|
||||
if (DEBUG) console.trace("calcCssFixups start for " + this.theme + " (checking " +
|
||||
document.styleSheets.length +
|
||||
" stylesheets)");
|
||||
|
||||
this.cssFixups[this.theme] = [];
|
||||
|
||||
for (let i = 0; i < document.styleSheets.length; i++) {
|
||||
const ss = document.styleSheets[i];
|
||||
if (!ss) continue; // well done safari >:(
|
||||
// Chromium apparently sometimes returns null here; unsure why.
|
||||
// see $14534907369972FRXBx:matrix.org in HQ
|
||||
// ...ah, it's because there's a third party extension like
|
||||
// privacybadger inserting its own stylesheet in there with a
|
||||
// resource:// URI or something which results in a XSS error.
|
||||
// See also #vector:matrix.org/$145357669685386ebCfr:matrix.org
|
||||
// ...except some browsers apparently return stylesheets without
|
||||
// hrefs, which we have no choice but ignore right now
|
||||
|
||||
// XXX seriously? we are hardcoding the name of vector's CSS file in
|
||||
// here?
|
||||
//
|
||||
// Why do we need to limit it to vector's CSS file anyway - if there
|
||||
// are other CSS files affecting the doc don't we want to apply the
|
||||
// same transformations to them?
|
||||
//
|
||||
// Iterating through the CSS looking for matches to hack on feels
|
||||
// pretty horrible anyway. And what if the application skin doesn't use
|
||||
// Vector Green as its primary color?
|
||||
// --richvdh
|
||||
|
||||
// Yes, tinting assumes that you are using the Riot skin for now.
|
||||
// The right solution will be to move the CSS over to react-sdk.
|
||||
// And yes, the default assets for the base skin might as well use
|
||||
// Vector Green as any other colour.
|
||||
// --matthew
|
||||
|
||||
if (ss.href && !ss.href.match(new RegExp('/theme-' + this.theme + '.css$'))) continue;
|
||||
if (ss.disabled) continue;
|
||||
if (!ss.cssRules) continue;
|
||||
|
||||
for (let j = 0; j < ss.cssRules.length; j++) {
|
||||
const rule = ss.cssRules[j];
|
||||
if (!rule.style) continue;
|
||||
if (rule.selectorText && rule.selectorText.match(/#mx_theme/)) continue;
|
||||
for (let k = 0; k < this.cssAttrs.length; k++) {
|
||||
const attr = this.cssAttrs[k];
|
||||
for (let l = 0; l < this.keyRgb.length; l++) {
|
||||
if (rule.style[attr] === this.keyRgb[l]) {
|
||||
this.cssFixups[this.theme].push({
|
||||
style: rule.style,
|
||||
attr: attr,
|
||||
index: l,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DEBUG) console.log("calcCssFixups end (" +
|
||||
this.cssFixups[this.theme].length +
|
||||
" fixups)");
|
||||
}
|
||||
|
||||
applyCssFixups() {
|
||||
if (DEBUG) console.log("applyCssFixups start (" +
|
||||
this.cssFixups[this.theme].length +
|
||||
" fixups)");
|
||||
for (let i = 0; i < this.cssFixups[this.theme].length; i++) {
|
||||
const cssFixup = this.cssFixups[this.theme][i];
|
||||
cssFixup.style[cssFixup.attr] = this.colors[cssFixup.index];
|
||||
}
|
||||
if (DEBUG) console.log("applyCssFixups end");
|
||||
}
|
||||
|
||||
// XXX: we could just move this all into TintableSvg, but as it's so similar
|
||||
// to the CSS fixup stuff in Tinter (just that the fixups are stored in TintableSvg)
|
||||
// keeping it here for now.
|
||||
calcSvgFixups: function(svgs) {
|
||||
calcSvgFixups(svgs) {
|
||||
// go through manually fixing up SVG colours.
|
||||
// we could do this by stylesheets, but keeping the stylesheets
|
||||
// updated would be a PITA, so just brute-force search for the
|
||||
|
@ -265,10 +313,12 @@ module.exports = {
|
|||
const tags = svgDoc.getElementsByTagName("*");
|
||||
for (let j = 0; j < tags.length; j++) {
|
||||
const tag = tags[j];
|
||||
for (let k = 0; k < svgAttrs.length; k++) {
|
||||
const attr = svgAttrs[k];
|
||||
for (let l = 0; l < keyHex.length; l++) {
|
||||
if (tag.getAttribute(attr) && tag.getAttribute(attr).toUpperCase() === keyHex[l]) {
|
||||
for (let k = 0; k < this.svgAttrs.length; k++) {
|
||||
const attr = this.svgAttrs[k];
|
||||
for (let l = 0; l < this.keyHex.length; l++) {
|
||||
if (tag.getAttribute(attr) &&
|
||||
tag.getAttribute(attr).toUpperCase() === this.keyHex[l])
|
||||
{
|
||||
fixups.push({
|
||||
node: tag,
|
||||
attr: attr,
|
||||
|
@ -282,14 +332,19 @@ module.exports = {
|
|||
if (DEBUG) console.log("calcSvgFixups end");
|
||||
|
||||
return fixups;
|
||||
},
|
||||
}
|
||||
|
||||
applySvgFixups: function(fixups) {
|
||||
applySvgFixups(fixups) {
|
||||
if (DEBUG) console.log("applySvgFixups start for " + fixups);
|
||||
for (let i = 0; i < fixups.length; i++) {
|
||||
const svgFixup = fixups[i];
|
||||
svgFixup.node.setAttribute(svgFixup.attr, colors[svgFixup.index]);
|
||||
svgFixup.node.setAttribute(svgFixup.attr, this.colors[svgFixup.index]);
|
||||
}
|
||||
if (DEBUG) console.log("applySvgFixups end");
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (global.singletonTinter === undefined) {
|
||||
global.singletonTinter = new Tinter();
|
||||
}
|
||||
export default global.singletonTinter;
|
||||
|
|
|
@ -287,6 +287,9 @@ module.exports = React.createClass({
|
|||
this._windowWidth = 10000;
|
||||
this.handleResize();
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
|
||||
// check we have the right tint applied for this theme
|
||||
Tinter.tint();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
@ -886,7 +889,7 @@ module.exports = React.createClass({
|
|||
*/
|
||||
_onSetTheme: function(theme) {
|
||||
if (!theme) {
|
||||
theme = 'light';
|
||||
theme = this.props.config.default_theme || 'light';
|
||||
}
|
||||
|
||||
// look for the stylesheet elements.
|
||||
|
@ -915,6 +918,10 @@ module.exports = React.createClass({
|
|||
});
|
||||
styleElements[theme].disabled = false;
|
||||
|
||||
Tinter.setTheme(theme);
|
||||
const colors = Tinter.getCurrentColors();
|
||||
Tinter.tint(colors[0], colors[1]);
|
||||
|
||||
if (theme === 'dark') {
|
||||
// abuse the tinter to change all the SVG's #fff to #2d2d2d
|
||||
// XXX: obviously this shouldn't be hardcoded here.
|
||||
|
|
|
@ -114,6 +114,7 @@ const CRYPTO_SETTINGS = [
|
|||
const THEMES = [
|
||||
{ label: _td('Light theme'), value: 'light' },
|
||||
{ label: _td('Dark theme'), value: 'dark' },
|
||||
{ label: _td('Status.im theme'), value: 'status' },
|
||||
];
|
||||
|
||||
const IgnoredUser = React.createClass({
|
||||
|
@ -135,7 +136,7 @@ const IgnoredUser = React.createClass({
|
|||
render: function() {
|
||||
return (
|
||||
<li>
|
||||
<AccessibleButton onClick={this._onUnignoreClick} className="mx_UserSettings_button mx_UserSettings_buttonSmall">
|
||||
<AccessibleButton onClick={this._onUnignoreClick} className="mx_textButton">
|
||||
{ _t("Unignore") }
|
||||
</AccessibleButton>
|
||||
{ this.props.userId }
|
||||
|
|
|
@ -17,13 +17,13 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
const sdk = require('../../../index');
|
||||
const Modal = require("../../../Modal");
|
||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
import sdk from '../../../index';
|
||||
import Modal from "../../../Modal";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
|
||||
const PasswordReset = require("../../../PasswordReset");
|
||||
import PasswordReset from "../../../PasswordReset";
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ForgotPassword',
|
||||
|
@ -154,6 +154,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
const LoginPage = sdk.getComponent("login.LoginPage");
|
||||
const LoginHeader = sdk.getComponent("login.LoginHeader");
|
||||
const LoginFooter = sdk.getComponent("login.LoginFooter");
|
||||
const ServerConfig = sdk.getComponent("login.ServerConfig");
|
||||
|
@ -165,7 +166,7 @@ module.exports = React.createClass({
|
|||
resetPasswordJsx = <Spinner />;
|
||||
} else if (this.state.progress === "sent_email") {
|
||||
resetPasswordJsx = (
|
||||
<div>
|
||||
<div className="mx_Login_prompt">
|
||||
{ _t("An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", { emailAddress: this.state.email }) }
|
||||
<br />
|
||||
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
||||
|
@ -174,7 +175,7 @@ module.exports = React.createClass({
|
|||
);
|
||||
} else if (this.state.progress === "complete") {
|
||||
resetPasswordJsx = (
|
||||
<div>
|
||||
<div className="mx_Login_prompt">
|
||||
<p>{ _t('Your password has been reset') }.</p>
|
||||
<p>{ _t('You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device') }.</p>
|
||||
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
|
||||
|
@ -182,6 +183,20 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
);
|
||||
} else {
|
||||
let serverConfigSection;
|
||||
if (!config.disable_custom_urls) {
|
||||
serverConfigSection = (
|
||||
<ServerConfig ref="serverConfig"
|
||||
withToggleButton={true}
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
defaultIsUrl={this.props.defaultIsUrl}
|
||||
customHsUrl={this.props.customHsUrl}
|
||||
customIsUrl={this.props.customIsUrl}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
delayTimeMs={0} />
|
||||
);
|
||||
}
|
||||
|
||||
resetPasswordJsx = (
|
||||
<div>
|
||||
<div className="mx_Login_prompt">
|
||||
|
@ -209,16 +224,7 @@ module.exports = React.createClass({
|
|||
<br />
|
||||
<input className="mx_Login_submit" type="submit" value={_t('Send Reset Email')} />
|
||||
</form>
|
||||
<ServerConfig ref="serverConfig"
|
||||
withToggleButton={true}
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
defaultIsUrl={this.props.defaultIsUrl}
|
||||
customHsUrl={this.props.customHsUrl}
|
||||
customIsUrl={this.props.customIsUrl}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
delayTimeMs={0} />
|
||||
<div className="mx_Login_error">
|
||||
</div>
|
||||
{ serverConfigSection }
|
||||
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
||||
{ _t('Return to login screen') }
|
||||
</a>
|
||||
|
@ -233,12 +239,12 @@ module.exports = React.createClass({
|
|||
|
||||
|
||||
return (
|
||||
<div className="mx_Login">
|
||||
<LoginPage>
|
||||
<div className="mx_Login_box">
|
||||
<LoginHeader />
|
||||
{ resetPasswordJsx }
|
||||
</div>
|
||||
</div>
|
||||
</LoginPage>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -23,6 +23,7 @@ import * as languageHandler from '../../../languageHandler';
|
|||
import sdk from '../../../index';
|
||||
import Login from '../../../Login';
|
||||
import PlatformPeg from '../../../PlatformPeg';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
|
||||
// For validating phone numbers without country codes
|
||||
|
@ -105,7 +106,22 @@ module.exports = React.createClass({
|
|||
if (error.httpStatus == 400 && usingEmail) {
|
||||
errorText = _t('This Home Server does not support login using email address.');
|
||||
} else if (error.httpStatus === 401 || error.httpStatus === 403) {
|
||||
errorText = _t('Incorrect username and/or password.');
|
||||
if (SdkConfig.get().disable_custom_urls) {
|
||||
errorText = (
|
||||
<div>
|
||||
<div>{ _t('Incorrect username and/or password.') }</div>
|
||||
<div className="mx_Login_smallError">
|
||||
{ _t('Please note you are logging into the %(hs)s server, not matrix.org.',
|
||||
{
|
||||
hs: this.props.defaultHsUrl.replace(/^https?:\/\//, '')
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
errorText = _t('Incorrect username and/or password.');
|
||||
}
|
||||
} else {
|
||||
// other errors, not specific to doing a password login
|
||||
errorText = this._errorTextFromError(error);
|
||||
|
@ -329,6 +345,7 @@ module.exports = React.createClass({
|
|||
|
||||
render: function() {
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
const LoginPage = sdk.getComponent("login.LoginPage");
|
||||
const LoginHeader = sdk.getComponent("login.LoginHeader");
|
||||
const LoginFooter = sdk.getComponent("login.LoginFooter");
|
||||
const ServerConfig = sdk.getComponent("login.ServerConfig");
|
||||
|
@ -343,43 +360,69 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
let returnToAppJsx;
|
||||
/*
|
||||
// with the advent of ILAG I don't think we need this any more
|
||||
if (this.props.onCancelClick) {
|
||||
returnToAppJsx =
|
||||
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
|
||||
{ _t('Return to app') }
|
||||
</a>;
|
||||
}
|
||||
*/
|
||||
|
||||
if (!SdkConfig.get().disable_custom_urls) {
|
||||
serverConfig = <ServerConfig ref="serverConfig"
|
||||
withToggleButton={true}
|
||||
customHsUrl={this.props.customHsUrl}
|
||||
customIsUrl={this.props.customIsUrl}
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
defaultIsUrl={this.props.defaultIsUrl}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
delayTimeMs={1000} />;
|
||||
}
|
||||
|
||||
let serverConfig;
|
||||
let header;
|
||||
|
||||
// FIXME: remove status.im theme tweaks
|
||||
const theme = UserSettingsStore.getTheme();
|
||||
if (theme !== "status") {
|
||||
header = <h2>{ _t('Sign in') }</h2>;
|
||||
}
|
||||
else {
|
||||
if (!this.state.errorText) {
|
||||
header = <h2>{ _t('Sign in to get started') }</h2>;
|
||||
}
|
||||
}
|
||||
|
||||
let errorTextSection;
|
||||
if (this.state.errorText) {
|
||||
errorTextSection = (
|
||||
<div className="mx_Login_error">
|
||||
{ this.state.errorText }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_Login">
|
||||
<LoginPage>
|
||||
<div className="mx_Login_box">
|
||||
<LoginHeader />
|
||||
<div>
|
||||
<h2>{ _t('Sign in') }
|
||||
{ loader }
|
||||
</h2>
|
||||
{ header }
|
||||
{ errorTextSection }
|
||||
{ this.componentForStep(this.state.currentFlow) }
|
||||
<ServerConfig ref="serverConfig"
|
||||
withToggleButton={true}
|
||||
customHsUrl={this.props.customHsUrl}
|
||||
customIsUrl={this.props.customIsUrl}
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
defaultIsUrl={this.props.defaultIsUrl}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
delayTimeMs={1000} />
|
||||
<div className="mx_Login_error">
|
||||
{ this.state.errorText }
|
||||
</div>
|
||||
{ serverConfig }
|
||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||
{ _t('Create an account') }
|
||||
</a>
|
||||
{ loginAsGuestJsx }
|
||||
{ returnToAppJsx }
|
||||
{ this._renderLanguageSetting() }
|
||||
{ !SdkConfig.get().disable_login_language_selector ? this._renderLanguageSetting() : '' }
|
||||
<LoginFooter />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LoginPage>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -59,9 +59,10 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
const ChangeDisplayName = sdk.getComponent('settings.ChangeDisplayName');
|
||||
const ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
|
||||
const LoginPage = sdk.getComponent('login.LoginPage');
|
||||
const LoginHeader = sdk.getComponent('login.LoginHeader');
|
||||
return (
|
||||
<div className="mx_Login">
|
||||
<LoginPage>
|
||||
<div className="mx_Login_box">
|
||||
<LoginHeader />
|
||||
<div className="mx_Login_profile">
|
||||
|
@ -74,7 +75,7 @@ module.exports = React.createClass({
|
|||
{ this.state.errorString }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LoginPage>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -26,6 +26,8 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
|
|||
import RegistrationForm from '../../views/login/RegistrationForm';
|
||||
import RtsClient from '../../../RtsClient';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import UserSettingsStore from '../../../UserSettingsStore';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
|
||||
const MIN_PASSWORD_LENGTH = 6;
|
||||
|
||||
|
@ -322,10 +324,13 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
const LoginHeader = sdk.getComponent('login.LoginHeader');
|
||||
const LoginFooter = sdk.getComponent('login.LoginFooter');
|
||||
const LoginPage = sdk.getComponent('login.LoginPage');
|
||||
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
const ServerConfig = sdk.getComponent('views.login.ServerConfig');
|
||||
|
||||
const theme = UserSettingsStore.getTheme();
|
||||
|
||||
let registerBody;
|
||||
if (this.state.doingUIAuth) {
|
||||
registerBody = (
|
||||
|
@ -344,9 +349,19 @@ module.exports = React.createClass({
|
|||
} else if (this.state.busy || this.state.teamServerBusy) {
|
||||
registerBody = <Spinner />;
|
||||
} else {
|
||||
let errorSection;
|
||||
if (this.state.errorText) {
|
||||
errorSection = <div className="mx_Login_error">{ this.state.errorText }</div>;
|
||||
let serverConfigSection;
|
||||
if (!SdkConfig.get().disable_custom_urls) {
|
||||
serverConfigSection = (
|
||||
<ServerConfig ref="serverConfig"
|
||||
withToggleButton={true}
|
||||
customHsUrl={this.props.customHsUrl}
|
||||
customIsUrl={this.props.customIsUrl}
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
defaultIsUrl={this.props.defaultIsUrl}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
delayTimeMs={1000}
|
||||
/>
|
||||
);
|
||||
}
|
||||
registerBody = (
|
||||
<div>
|
||||
|
@ -362,21 +377,14 @@ module.exports = React.createClass({
|
|||
onRegisterClick={this.onFormSubmit}
|
||||
onTeamSelected={this.onTeamSelected}
|
||||
/>
|
||||
{ errorSection }
|
||||
<ServerConfig ref="serverConfig"
|
||||
withToggleButton={true}
|
||||
customHsUrl={this.props.customHsUrl}
|
||||
customIsUrl={this.props.customIsUrl}
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
defaultIsUrl={this.props.defaultIsUrl}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
delayTimeMs={1000}
|
||||
/>
|
||||
{ serverConfigSection }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let returnToAppJsx;
|
||||
/*
|
||||
// with the advent of ILAG I don't think we need this any more
|
||||
if (this.props.onCancelClick) {
|
||||
returnToAppJsx = (
|
||||
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
|
||||
|
@ -384,8 +392,32 @@ module.exports = React.createClass({
|
|||
</a>
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
let header;
|
||||
let errorText;
|
||||
// FIXME: remove hardcoded Status team tweaks at some point
|
||||
if (theme === 'status' && this.state.errorText) {
|
||||
header = <div className="mx_Login_error">{ this.state.errorText }</div>;
|
||||
}
|
||||
else {
|
||||
header = <h2>{ _t('Create an account') }</h2>;
|
||||
if (this.state.errorText) {
|
||||
errorText = <div className="mx_Login_error">{ this.state.errorText }</div>;
|
||||
}
|
||||
}
|
||||
|
||||
let signIn;
|
||||
if (!this.state.doingUIAuth) {
|
||||
signIn = (
|
||||
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
||||
{ theme === 'status' ? _t('Sign in') : _t('I already have an account') }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_Login">
|
||||
<LoginPage>
|
||||
<div className="mx_Login_box">
|
||||
<LoginHeader
|
||||
icon={this.state.teamSelected ?
|
||||
|
@ -393,15 +425,14 @@ module.exports = React.createClass({
|
|||
this.state.teamSelected.domain + "/icon.png" :
|
||||
null}
|
||||
/>
|
||||
<h2>{ _t('Create an account') }</h2>
|
||||
{ header }
|
||||
{ registerBody }
|
||||
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
||||
{ _t('I already have an account') }
|
||||
</a>
|
||||
{ signIn }
|
||||
{ errorText }
|
||||
{ returnToAppJsx }
|
||||
<LoginFooter />
|
||||
</div>
|
||||
</div>
|
||||
</LoginPage>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
59
src/components/views/login/LoginPage.js
Normal file
59
src/components/views/login/LoginPage.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import UserSettingsStore from '../../../UserSettingsStore';
|
||||
|
||||
const React = require('react');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'LoginPage',
|
||||
|
||||
render: function() {
|
||||
// FIXME: this should be turned into a proper skin with a StatusLoginPage component
|
||||
if (UserSettingsStore.getTheme() === 'status') {
|
||||
return (
|
||||
<div className="mx_StatusLogin">
|
||||
<div className="mx_StatusLogin_brand">
|
||||
<img src="themes/status/img/logo.svg" alt="Status" width="221" height="53" />
|
||||
</div>
|
||||
<div className="mx_StatusLogin_content">
|
||||
<div className="mx_StatusLogin_header">
|
||||
<h1>Status Community Chat</h1>
|
||||
<div className="mx_StatusLogin_subtitle">
|
||||
A safer, decentralised communication
|
||||
platform <a href="https://riot.im">powered by Riot</a>
|
||||
</div>
|
||||
</div>
|
||||
{ this.props.children }
|
||||
<div className="mx_StatusLogin_footer">
|
||||
<p>This channel is for our development community.</p>
|
||||
<p>Interested in SNT and discussions on the cryptocurrency market?</p>
|
||||
<p><a href="https://t.me/StatusNetworkChat" target="_blank" className="mx_StatusLogin_footer_cta">Join Telegram Chat</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="mx_Login">
|
||||
{ this.props.children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
|
@ -20,7 +20,7 @@ import classNames from 'classnames';
|
|||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {field_input_incorrect} from '../../../UiEffects';
|
||||
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
|
||||
/**
|
||||
* A pure UI component which displays a username/password form.
|
||||
|
@ -144,7 +144,10 @@ class PasswordLogin extends React.Component {
|
|||
type="text"
|
||||
name="username" // make it a little easier for browser's remember-password
|
||||
onChange={this.onUsernameChanged}
|
||||
placeholder={_t('User name')}
|
||||
placeholder={ SdkConfig.get().disable_custom_urls ?
|
||||
_t("Username on %(hs)s", {
|
||||
hs: this.props.hsUrl.replace(/^https?:\/\//, '')
|
||||
}) : _t("User name")}
|
||||
value={this.state.username}
|
||||
autoFocus
|
||||
disabled={disabled}
|
||||
|
@ -210,9 +213,9 @@ class PasswordLogin extends React.Component {
|
|||
|
||||
const loginField = this.renderLoginField(this.state.loginType, matrixIdText === '');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={this.onSubmitForm}>
|
||||
let loginType;
|
||||
if (!SdkConfig.get().disable_3pid_login) {
|
||||
loginType = (
|
||||
<div className="mx_Login_type_container">
|
||||
<label className="mx_Login_type_label">{ _t('Sign in with') }</label>
|
||||
<Dropdown
|
||||
|
@ -225,6 +228,13 @@ class PasswordLogin extends React.Component {
|
|||
<span key={PasswordLogin.LOGIN_FIELD_PHONE}>{ _t('Phone') }</span>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={this.onSubmitForm}>
|
||||
{ loginType }
|
||||
{ loginField }
|
||||
<input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password"
|
||||
name="password"
|
||||
|
|
|
@ -22,6 +22,8 @@ import Email from '../../../email';
|
|||
import { looksValid as phoneNumberLooksValid } from '../../../phonenumber';
|
||||
import Modal from '../../../Modal';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import UserSettingsStore from '../../../UserSettingsStore';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
|
||||
const FIELD_EMAIL = 'field_email';
|
||||
const FIELD_PHONE_COUNTRY = 'field_phone_country';
|
||||
|
@ -122,7 +124,7 @@ module.exports = React.createClass({
|
|||
password: this.refs.password.value.trim(),
|
||||
email: email,
|
||||
phoneCountry: this.state.phoneCountry,
|
||||
phoneNumber: this.refs.phoneNumber.value.trim(),
|
||||
phoneNumber: this.refs.phoneNumber ? this.refs.phoneNumber.value.trim() : '',
|
||||
});
|
||||
|
||||
if (promise) {
|
||||
|
@ -180,7 +182,7 @@ module.exports = React.createClass({
|
|||
this.markFieldValid(field_id, emailValid, "RegistrationForm.ERR_EMAIL_INVALID");
|
||||
break;
|
||||
case FIELD_PHONE_NUMBER:
|
||||
const phoneNumber = this.refs.phoneNumber.value;
|
||||
const phoneNumber = this.refs.phoneNumber ? this.refs.phoneNumber.value : '';
|
||||
const phoneNumberValid = phoneNumber === '' || phoneNumberLooksValid(phoneNumber);
|
||||
this.markFieldValid(field_id, phoneNumberValid, "RegistrationForm.ERR_PHONE_NUMBER_INVALID");
|
||||
break;
|
||||
|
@ -273,10 +275,14 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
const self = this;
|
||||
|
||||
const theme = UserSettingsStore.getTheme();
|
||||
// FIXME: remove hardcoded Status team tweaks at some point
|
||||
const emailPlaceholder = theme === 'status' ? _t("Email address") : _t("Email address (optional)");
|
||||
|
||||
const emailSection = (
|
||||
<div>
|
||||
<input type="text" ref="email"
|
||||
autoFocus={true} placeholder={_t("Email address (optional)")}
|
||||
autoFocus={true} placeholder={ emailPlaceholder }
|
||||
defaultValue={this.props.defaultEmail}
|
||||
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
||||
onBlur={function() {self.validateField(FIELD_EMAIL);}}
|
||||
|
@ -306,28 +312,31 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
|
||||
const phoneSection = (
|
||||
<div className="mx_Login_phoneSection">
|
||||
<CountryDropdown ref="phone_country" onOptionChange={this._onPhoneCountryChange}
|
||||
className="mx_Login_phoneCountry mx_Login_field_prefix"
|
||||
value={this.state.phoneCountry}
|
||||
isSmall={true}
|
||||
showPrefix={true}
|
||||
/>
|
||||
<input type="text" ref="phoneNumber"
|
||||
placeholder={_t("Mobile phone number (optional)")}
|
||||
defaultValue={this.props.defaultPhoneNumber}
|
||||
className={this._classForField(
|
||||
FIELD_PHONE_NUMBER,
|
||||
'mx_Login_phoneNumberField',
|
||||
'mx_Login_field',
|
||||
'mx_Login_field_has_prefix',
|
||||
)}
|
||||
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
|
||||
value={self.state.phoneNumber}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
let phoneSection;
|
||||
if (!SdkConfig.get().disable_3pid_login) {
|
||||
phoneSection = (
|
||||
<div className="mx_Login_phoneSection">
|
||||
<CountryDropdown ref="phone_country" onOptionChange={this._onPhoneCountryChange}
|
||||
className="mx_Login_phoneCountry mx_Login_field_prefix"
|
||||
value={this.state.phoneCountry}
|
||||
isSmall={true}
|
||||
showPrefix={true}
|
||||
/>
|
||||
<input type="text" ref="phoneNumber"
|
||||
placeholder={_t("Mobile phone number (optional)")}
|
||||
defaultValue={this.props.defaultPhoneNumber}
|
||||
className={this._classForField(
|
||||
FIELD_PHONE_NUMBER,
|
||||
'mx_Login_phoneNumberField',
|
||||
'mx_Login_field',
|
||||
'mx_Login_field_has_prefix',
|
||||
)}
|
||||
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
|
||||
value={self.state.phoneNumber}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const registerButton = (
|
||||
<input className="mx_Login_submit" type="submit" value={_t("Register")} />
|
||||
|
|
|
@ -26,7 +26,7 @@ import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
|||
|
||||
const ROOM_COLORS = [
|
||||
// magic room default values courtesy of Ribot
|
||||
["#76cfa6", "#eaf5f0"],
|
||||
[Tinter.getKeyRgb()[0], Tinter.getKeyRgb()[1]],
|
||||
["#81bddb", "#eaf1f4"],
|
||||
["#bd79cb", "#f3eaf5"],
|
||||
["#c65d94", "#f5eaef"],
|
||||
|
|
|
@ -479,6 +479,7 @@
|
|||
"Sign in with": "Sign in with",
|
||||
"Email address": "Email address",
|
||||
"Sign in": "Sign in",
|
||||
"Sign in to get started": "Sign in to get started",
|
||||
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?",
|
||||
"Email address (optional)": "Email address (optional)",
|
||||
"You are registering with %(SelectedTeamName)s": "You are registering with %(SelectedTeamName)s",
|
||||
|
@ -923,5 +924,8 @@
|
|||
"This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.",
|
||||
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.",
|
||||
"File to import": "File to import",
|
||||
"Import": "Import"
|
||||
"Import": "Import",
|
||||
"Status.im theme": "Status.im theme",
|
||||
"Please note you are logging into the %(hs)s server, not matrix.org.": "Please note you are logging into the %(hs)s server, not matrix.org.",
|
||||
"Username on %(hs)s": "Username on %(hs)s"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue