Bring over email address management
This commit is contained in:
parent
fa1ce61a06
commit
aa7afe819f
11 changed files with 342 additions and 11 deletions
|
@ -127,6 +127,7 @@
|
||||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||||
@import "./views/rooms/_WhoIsTypingTile.scss";
|
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||||
@import "./views/settings/_DevicesPanel.scss";
|
@import "./views/settings/_DevicesPanel.scss";
|
||||||
|
@import "./views/settings/_EmailAddresses.scss";
|
||||||
@import "./views/settings/_IntegrationsManager.scss";
|
@import "./views/settings/_IntegrationsManager.scss";
|
||||||
@import "./views/settings/_KeyBackupPanel.scss";
|
@import "./views/settings/_KeyBackupPanel.scss";
|
||||||
@import "./views/settings/_Notifications.scss";
|
@import "./views/settings/_Notifications.scss";
|
||||||
|
|
|
@ -42,3 +42,35 @@ limitations under the License.
|
||||||
color: $button-primary-disabled-fg-color;
|
color: $button-primary-disabled-fg-color;
|
||||||
background-color: $button-primary-disabled-bg-color;
|
background-color: $button-primary-disabled-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_primary_sm {
|
||||||
|
padding: 5px 12px !important;
|
||||||
|
color: $button-primary-fg-color;
|
||||||
|
background-color: $button-primary-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_primary_sm.mx_AccessibleButton_disabled {
|
||||||
|
color: $button-primary-disabled-fg-color;
|
||||||
|
background-color: $button-primary-disabled-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_danger {
|
||||||
|
color: $button-danger-fg-color;
|
||||||
|
background-color: $button-danger-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled {
|
||||||
|
color: $button-danger-disabled-fg-color;
|
||||||
|
background-color: $button-danger-disabled-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_danger_sm {
|
||||||
|
padding: 5px 12px !important;
|
||||||
|
color: $button-danger-fg-color;
|
||||||
|
background-color: $button-danger-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_danger_sm.mx_AccessibleButton_disabled {
|
||||||
|
color: $button-danger-disabled-fg-color;
|
||||||
|
background-color: $button-danger-disabled-bg-color;
|
||||||
|
}
|
41
res/css/views/settings/_EmailAddresses.scss
Normal file
41
res/css/views/settings/_EmailAddresses.scss
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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.
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_ExistingEmailAddress {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ExistingEmailAddress_delete {
|
||||||
|
margin-right: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ExistingEmailAddress_email {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ExistingEmailAddress_promptText {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ExistingEmailAddress_confirmBtn {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EmailAddresses_new .mx_Field input {
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
}
|
|
@ -30,4 +30,8 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_GeneralSettingsTab_changePassword .mx_Field:first-child {
|
.mx_GeneralSettingsTab_changePassword .mx_Field:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_GeneralSettingsTab_accountSection > .mx_EmailAddresses {
|
||||||
|
margin-right: 100px; // Align with the other fields on the page
|
||||||
}
|
}
|
|
@ -211,6 +211,10 @@ $button-primary-fg-color: #ffffff;
|
||||||
$button-primary-bg-color: #7ac9a1;
|
$button-primary-bg-color: #7ac9a1;
|
||||||
$button-primary-disabled-fg-color: #ffffff;
|
$button-primary-disabled-fg-color: #ffffff;
|
||||||
$button-primary-disabled-bg-color: #bce4d0;
|
$button-primary-disabled-bg-color: #bce4d0;
|
||||||
|
$button-danger-fg-color: #ffffff;
|
||||||
|
$button-danger-bg-color: #f56679;
|
||||||
|
$button-danger-disabled-fg-color: #ffffff;
|
||||||
|
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
|
||||||
|
|
||||||
// unused?
|
// unused?
|
||||||
$progressbar-color: #000;
|
$progressbar-color: #000;
|
||||||
|
|
|
@ -207,6 +207,10 @@ $button-primary-fg-color: #ffffff;
|
||||||
$button-primary-bg-color: #7ac9a1;
|
$button-primary-bg-color: #7ac9a1;
|
||||||
$button-primary-disabled-fg-color: #ffffff;
|
$button-primary-disabled-fg-color: #ffffff;
|
||||||
$button-primary-disabled-bg-color: #bce4d0;
|
$button-primary-disabled-bg-color: #bce4d0;
|
||||||
|
$button-danger-fg-color: #ffffff;
|
||||||
|
$button-danger-bg-color: #f56679;
|
||||||
|
$button-danger-disabled-fg-color: #ffffff;
|
||||||
|
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
|
||||||
|
|
||||||
// unused?
|
// unused?
|
||||||
$progressbar-color: #000;
|
$progressbar-color: #000;
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { _t } from './languageHandler';
|
||||||
* the client owns the given email address, which is then passed to the
|
* the client owns the given email address, which is then passed to the
|
||||||
* add threepid API on the homeserver.
|
* add threepid API on the homeserver.
|
||||||
*/
|
*/
|
||||||
class AddThreepid {
|
export default class AddThreepid {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.clientSecret = MatrixClientPeg.get().generateClientSecret();
|
this.clientSecret = MatrixClientPeg.get().generateClientSecret();
|
||||||
}
|
}
|
||||||
|
@ -124,5 +124,3 @@ class AddThreepid {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AddThreepid;
|
|
||||||
|
|
|
@ -38,6 +38,13 @@ export default class Field extends React.PureComponent {
|
||||||
return this.refs.fieldInput.value;
|
return this.refs.fieldInput.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set value(newValue) {
|
||||||
|
if (!this.refs.fieldInput) {
|
||||||
|
throw new Error("No field input reference");
|
||||||
|
}
|
||||||
|
this.refs.fieldInput.value = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const extraProps = Object.assign({}, this.props);
|
const extraProps = Object.assign({}, this.props);
|
||||||
|
|
||||||
|
|
231
src/components/views/settings/EmailAddresses.js
Normal file
231
src/components/views/settings/EmailAddresses.js
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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.
|
||||||
|
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 PropTypes from 'prop-types';
|
||||||
|
import {_t} from "../../../languageHandler";
|
||||||
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
|
import Field from "../elements/Field";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import * as Email from "../../../email";
|
||||||
|
import AddThreepid from "../../../AddThreepid";
|
||||||
|
const sdk = require('../../../index');
|
||||||
|
const Modal = require("../../../Modal");
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: Improve the UX for everything in here.
|
||||||
|
It's very much placeholder, but it gets the job done. The old way of handling
|
||||||
|
email addresses in user settings was to use dialogs to communicate state, however
|
||||||
|
due to our dialog system overriding dialogs (causing unmounts) this creates problems
|
||||||
|
for a sane UX. For instance, the user could easily end up entering an email address
|
||||||
|
and receive a dialog to verify the address, which then causes the component here
|
||||||
|
to forget what it was doing and ultimately fail. Dialogs are still used in some
|
||||||
|
places to communicate errors - these should be replaced with inline validation when
|
||||||
|
that is available.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class ExistingEmailAddress extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
email: PropTypes.object.isRequired,
|
||||||
|
onRemoved: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
verifyRemove: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRemove = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.setState({verifyRemove: true});
|
||||||
|
};
|
||||||
|
|
||||||
|
_onDontRemove = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.setState({verifyRemove: false});
|
||||||
|
};
|
||||||
|
|
||||||
|
_onActuallyRemove = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
MatrixClientPeg.get().deleteThreePid(this.props.email.medium, this.props.email.address).then(() => {
|
||||||
|
return this.props.onRemoved(this.props.email);
|
||||||
|
}).catch((err) => {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
console.error("Unable to remove contact information: " + err);
|
||||||
|
Modal.createTrackedDialog('Remove 3pid failed', '', ErrorDialog, {
|
||||||
|
title: _t("Unable to remove contact information"),
|
||||||
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.verifyRemove) {
|
||||||
|
return (
|
||||||
|
<div className="mx_ExistingEmailAddress">
|
||||||
|
<span className="mx_ExistingEmailAddress_promptText">
|
||||||
|
{_t("Are you sure?")}
|
||||||
|
</span>
|
||||||
|
<AccessibleButton onClick={this._onActuallyRemove} kind="primary_sm"
|
||||||
|
className="mx_ExistingEmailAddress_confirmBtn">
|
||||||
|
{_t("Yes")}
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton onClick={this._onDontRemove} kind="danger_sm"
|
||||||
|
className="mx_ExistingEmailAddress_confirmBtn">
|
||||||
|
{_t("No")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx_ExistingEmailAddress">
|
||||||
|
<img src={require("../../../../res/img/feather-icons/cancel.svg")} width={14} height={14}
|
||||||
|
onClick={this._onRemove} className="mx_ExistingEmailAddress_delete" alt={_t("Remove")} />
|
||||||
|
<span className="mx_ExistingEmailAddress_email">{this.props.email.address}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class EmailAddresses extends React.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
emails: [],
|
||||||
|
verifying: false,
|
||||||
|
addTask: null,
|
||||||
|
continueDisabled: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount(): void {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
client.getThreePids().then((addresses) => {
|
||||||
|
this.setState({emails: addresses.threepids.filter((a) => a.medium === 'email')});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRemoved = (address) => {
|
||||||
|
this.setState({emails: this.state.emails.filter((e) => e !== address)});
|
||||||
|
};
|
||||||
|
|
||||||
|
_onAddClick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!this.refs.newEmailAddress) return;
|
||||||
|
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
const email = this.refs.newEmailAddress.value;
|
||||||
|
|
||||||
|
// TODO: Inline field validation
|
||||||
|
if (!Email.looksValid(email)) {
|
||||||
|
Modal.createTrackedDialog('Invalid email address', '', ErrorDialog, {
|
||||||
|
title: _t("Invalid Email Address"),
|
||||||
|
description: _t("This doesn't appear to be a valid email address"),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = new AddThreepid();
|
||||||
|
this.setState({verifying: true, continueDisabled: true, addTask: task});
|
||||||
|
|
||||||
|
task.addEmailAddress(email, true).then(() => {
|
||||||
|
this.setState({continueDisabled: false});
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error("Unable to add email address " + email + " " + err);
|
||||||
|
this.setState({verifying: false, continueDisabled: false, addTask: null});
|
||||||
|
Modal.createTrackedDialog('Unable to add email address', '', ErrorDialog, {
|
||||||
|
title: _t("Unable to add email address"),
|
||||||
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_onContinueClick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.setState({continueDisabled: true});
|
||||||
|
this.state.addTask.checkEmailLinkClicked().then(() => {
|
||||||
|
const email = this.refs.newEmailAddress.value;
|
||||||
|
this.setState({
|
||||||
|
emails: [...this.state.emails, {address: email, medium: "email"}],
|
||||||
|
addTask: null,
|
||||||
|
continueDisabled: false,
|
||||||
|
verifying: false,
|
||||||
|
});
|
||||||
|
this.refs.newEmailAddress.value = "";
|
||||||
|
}).catch((err) => {
|
||||||
|
this.setState({continueDisabled: false});
|
||||||
|
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
console.error("Unable to verify email address: " + err);
|
||||||
|
Modal.createTrackedDialog('Unable to verify email address', '', ErrorDialog, {
|
||||||
|
title: _t("Unable to verify email address."),
|
||||||
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const existingEmailElements = this.state.emails.map((e) => {
|
||||||
|
return <ExistingEmailAddress email={e} onRemoved={this._onRemoved} key={e.address}/>;
|
||||||
|
});
|
||||||
|
|
||||||
|
let addButton = (
|
||||||
|
<AccessibleButton onClick={this._onAddClick} kind="primary">
|
||||||
|
{_t("Add")}
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
if (this.state.verifying) {
|
||||||
|
addButton = (
|
||||||
|
<div>
|
||||||
|
<div>{_t("We've sent you an email to verify your address. Please follow the instructions there and then click the button below.")}</div>
|
||||||
|
<AccessibleButton onClick={this._onContinueClick} kind="primary"
|
||||||
|
disabled={this.state.continueDisabled}>
|
||||||
|
{_t("Continue")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx_EmailAddresses">
|
||||||
|
{existingEmailElements}
|
||||||
|
<form onSubmit={this._onAddClick} autoComplete={false}
|
||||||
|
noValidate={true} className="mx_EmailAddresses_new">
|
||||||
|
<Field id="newEmailAddress" ref="newEmailAddress" label={_t("Email Address")}
|
||||||
|
type="text" autoComplete="off" disabled={this.state.verifying}/>
|
||||||
|
{addButton}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import PropTypes from "prop-types";
|
||||||
import {MatrixClient} from "matrix-js-sdk";
|
import {MatrixClient} from "matrix-js-sdk";
|
||||||
import { DragDropContext } from 'react-beautiful-dnd';
|
import { DragDropContext } from 'react-beautiful-dnd';
|
||||||
import ProfileSettings from "../ProfileSettings";
|
import ProfileSettings from "../ProfileSettings";
|
||||||
|
import EmailAddresses from "../EmailAddresses";
|
||||||
const sdk = require('../../../../index');
|
const sdk = require('../../../../index');
|
||||||
const Modal = require("../../../../Modal");
|
const Modal = require("../../../../Modal");
|
||||||
|
|
||||||
|
@ -95,12 +96,15 @@ export default class GeneralSettingsTab extends React.Component {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_SettingsTab_section">
|
<div className="mx_SettingsTab_section mx_GeneralSettingsTab_accountSection">
|
||||||
<span className="mx_SettingsTab_subheading">{_t("Account")}</span>
|
<span className="mx_SettingsTab_subheading">{_t("Account")}</span>
|
||||||
<p className="mx_SettingsTab_subsectionText">
|
<p className="mx_SettingsTab_subsectionText">
|
||||||
{_t("Set a new account password...")}
|
{_t("Set a new account password...")}
|
||||||
</p>
|
</p>
|
||||||
{passwordChangeForm}
|
{passwordChangeForm}
|
||||||
|
|
||||||
|
<span className="mx_SettingsTab_subheading">{_t("Email addresses")}</span>
|
||||||
|
<EmailAddresses />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,6 +352,17 @@
|
||||||
"Last seen": "Last seen",
|
"Last seen": "Last seen",
|
||||||
"Select devices": "Select devices",
|
"Select devices": "Select devices",
|
||||||
"Failed to set display name": "Failed to set display name",
|
"Failed to set display name": "Failed to set display name",
|
||||||
|
"Unable to remove contact information": "Unable to remove contact information",
|
||||||
|
"Are you sure?": "Are you sure?",
|
||||||
|
"Yes": "Yes",
|
||||||
|
"No": "No",
|
||||||
|
"Remove": "Remove",
|
||||||
|
"Invalid Email Address": "Invalid Email Address",
|
||||||
|
"This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address",
|
||||||
|
"Unable to add email address": "Unable to add email address",
|
||||||
|
"Unable to verify email address.": "Unable to verify email address.",
|
||||||
|
"We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.",
|
||||||
|
"Email Address": "Email Address",
|
||||||
"Disable Notifications": "Disable Notifications",
|
"Disable Notifications": "Disable Notifications",
|
||||||
"Enable Notifications": "Enable Notifications",
|
"Enable Notifications": "Enable Notifications",
|
||||||
"Delete Backup": "Delete Backup",
|
"Delete Backup": "Delete Backup",
|
||||||
|
@ -413,6 +424,7 @@
|
||||||
"Flair": "Flair",
|
"Flair": "Flair",
|
||||||
"Account": "Account",
|
"Account": "Account",
|
||||||
"Set a new account password...": "Set a new account password...",
|
"Set a new account password...": "Set a new account password...",
|
||||||
|
"Email addresses": "Email addresses",
|
||||||
"Language and region": "Language and region",
|
"Language and region": "Language and region",
|
||||||
"Theme": "Theme",
|
"Theme": "Theme",
|
||||||
"Account management": "Account management",
|
"Account management": "Account management",
|
||||||
|
@ -464,7 +476,6 @@
|
||||||
"Failed to toggle moderator status": "Failed to toggle moderator status",
|
"Failed to toggle moderator status": "Failed to toggle moderator status",
|
||||||
"Failed to change power level": "Failed to change power level",
|
"Failed to change power level": "Failed to change power level",
|
||||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
|
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
|
||||||
"Are you sure?": "Are you sure?",
|
|
||||||
"No devices with registered encryption keys": "No devices with registered encryption keys",
|
"No devices with registered encryption keys": "No devices with registered encryption keys",
|
||||||
"Devices": "Devices",
|
"Devices": "Devices",
|
||||||
"Unignore": "Unignore",
|
"Unignore": "Unignore",
|
||||||
|
@ -737,7 +748,6 @@
|
||||||
"Flair will not appear": "Flair will not appear",
|
"Flair will not appear": "Flair will not appear",
|
||||||
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?",
|
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?",
|
||||||
"Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.",
|
"Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.",
|
||||||
"Remove": "Remove",
|
|
||||||
"Failed to remove room from community": "Failed to remove room from community",
|
"Failed to remove room from community": "Failed to remove room from community",
|
||||||
"Failed to remove '%(roomName)s' from %(groupId)s": "Failed to remove '%(roomName)s' from %(groupId)s",
|
"Failed to remove '%(roomName)s' from %(groupId)s": "Failed to remove '%(roomName)s' from %(groupId)s",
|
||||||
"Something went wrong!": "Something went wrong!",
|
"Something went wrong!": "Something went wrong!",
|
||||||
|
@ -982,12 +992,8 @@
|
||||||
"We encountered an error trying to restore your previous session.": "We encountered an error trying to restore your previous session.",
|
"We encountered an error trying to restore your previous session.": "We encountered an error trying to restore your previous session.",
|
||||||
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.",
|
"If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.",
|
||||||
"Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.",
|
"Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.",
|
||||||
"Invalid Email Address": "Invalid Email Address",
|
|
||||||
"This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address",
|
|
||||||
"Verification Pending": "Verification Pending",
|
"Verification Pending": "Verification Pending",
|
||||||
"Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.",
|
"Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.",
|
||||||
"Unable to add email address": "Unable to add email address",
|
|
||||||
"Unable to verify email address.": "Unable to verify email address.",
|
|
||||||
"Email address": "Email address",
|
"Email address": "Email address",
|
||||||
"This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.",
|
"This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.",
|
||||||
"Skip": "Skip",
|
"Skip": "Skip",
|
||||||
|
@ -1253,7 +1259,6 @@
|
||||||
"Server may be unavailable or overloaded": "Server may be unavailable or overloaded",
|
"Server may be unavailable or overloaded": "Server may be unavailable or overloaded",
|
||||||
"Remove Contact Information?": "Remove Contact Information?",
|
"Remove Contact Information?": "Remove Contact Information?",
|
||||||
"Remove %(threePid)s?": "Remove %(threePid)s?",
|
"Remove %(threePid)s?": "Remove %(threePid)s?",
|
||||||
"Unable to remove contact information": "Unable to remove contact information",
|
|
||||||
"Refer a friend to Riot:": "Refer a friend to Riot:",
|
"Refer a friend to Riot:": "Refer a friend to Riot:",
|
||||||
"Interface Language": "Interface Language",
|
"Interface Language": "Interface Language",
|
||||||
"User Interface": "User Interface",
|
"User Interface": "User Interface",
|
||||||
|
|
Loading…
Reference in a new issue