Merge branch 'develop' into travis/cross-sign-setting

This commit is contained in:
Travis Ralston 2020-04-16 11:06:26 -06:00
commit db749519e7
18 changed files with 226 additions and 176 deletions

View file

@ -19,6 +19,7 @@ limitations under the License.
}
.mx_MessageContextMenu_field {
display: block;
padding: 3px 6px 3px 6px;
cursor: pointer;
white-space: nowrap;

View file

@ -51,7 +51,7 @@ async function confirmToDismiss(name) {
} else if (name === "m.cross_signing.self_signing") {
description = _t("If you cancel now, you won't complete verifying your other session.");
} else {
description = _t("If you cancel now, you won't complete your secret storage operation.");
description = _t("If you cancel now, you won't complete your operation.");
}
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");

View file

@ -25,6 +25,7 @@ import { _t } from '../../../../languageHandler';
import { accessSecretStorage } from '../../../../CrossSigningManager';
import SettingsStore from '../../../../settings/SettingsStore';
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
import {copyNode} from "../../../../utils/strings";
const PHASE_PASSPHRASE = 0;
const PHASE_PASSPHRASE_CONFIRM = 1;
@ -37,16 +38,6 @@ const PHASE_OPTOUT_CONFIRM = 6;
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
const PASSPHRASE_FEEDBACK_DELAY = 500; // How long after keystroke to offer passphrase feedback, ms.
// XXX: copied from ShareDialog: factor out into utils
function selectText(target) {
const range = document.createRange();
range.selectNodeContents(target);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
/*
* Walks the user through the process of creating an e2e key backup
* on the server.
@ -101,8 +92,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
}
_onCopyClick = () => {
selectText(this._recoveryKeyNode);
const successful = document.execCommand('copy');
const successful = copyNode(this._recoveryKeyNode);
if (successful) {
this.setState({
copied: true,
@ -272,7 +262,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
let helpText;
if (this.state.zxcvbnResult) {
if (this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE) {
helpText = _t("Great! This passphrase looks strong enough.");
helpText = _t("Great! This recovery passphrase looks strong enough.");
} else {
const suggestions = [];
for (let i = 0; i < this.state.zxcvbnResult.feedback.suggestions.length; ++i) {
@ -297,7 +287,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
)}</p>
<p>{_t(
"We'll store an encrypted copy of your keys on our server. " +
"Protect your backup with a passphrase to keep it secure.",
"Secure your backup with a recovery passphrase.",
)}</p>
<p>{_t("For maximum security, this should be different from your account password.")}</p>
@ -307,7 +297,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
onChange={this._onPassPhraseChange}
value={this.state.passPhrase}
className="mx_CreateKeyBackupDialog_passPhraseInput"
placeholder={_t("Enter a passphrase...")}
placeholder={_t("Enter a recovery passphrase...")}
autoFocus={true}
/>
<div className="mx_CreateKeyBackupDialog_passPhraseHelp">
@ -364,7 +354,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
<p>{_t(
"Please enter your passphrase a second time to confirm.",
"Please enter your recovery passphrase a second time to confirm.",
)}</p>
<div className="mx_CreateKeyBackupDialog_primaryContainer">
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
@ -373,7 +363,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
onChange={this._onPassPhraseConfirmChange}
value={this.state.passPhraseConfirm}
className="mx_CreateKeyBackupDialog_passPhraseInput"
placeholder={_t("Repeat your passphrase...")}
placeholder={_t("Repeat your recovery passphrase...")}
autoFocus={true}
/>
</div>
@ -393,7 +383,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
return <div>
<p>{_t(
"Your recovery key is a safety net - you can use it to restore " +
"access to your encrypted messages if you forget your passphrase.",
"access to your encrypted messages if you forget your recovery passphrase.",
)}</p>
<p>{_t(
"Keep a copy of it somewhere secure, like a password manager or even a safe.",
@ -487,9 +477,9 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
_titleForPhase(phase) {
switch (phase) {
case PHASE_PASSPHRASE:
return _t('Secure your backup with a passphrase');
return _t('Secure your backup with a recovery passphrase');
case PHASE_PASSPHRASE_CONFIRM:
return _t('Confirm your passphrase');
return _t('Confirm your recovery passphrase');
case PHASE_OPTOUT_CONFIRM:
return _t('Warning!');
case PHASE_SHOWKEY:

View file

@ -57,8 +57,7 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
</span>;
const newMethodDetected = <p>{_t(
"A new recovery passphrase and key for Secure " +
"Messages have been detected.",
"A new recovery passphrase and key for Secure Messages have been detected.",
)}</p>;
const hackWarning = <p className="warning">{_t(

View file

@ -24,6 +24,7 @@ import FileSaver from 'file-saver';
import { _t } from '../../../../languageHandler';
import Modal from '../../../../Modal';
import { promptForBackupPassphrase } from '../../../../CrossSigningManager';
import {copyNode} from "../../../../utils/strings";
const PHASE_LOADING = 0;
const PHASE_MIGRATE = 1;
@ -38,16 +39,6 @@ const PHASE_CONFIRM_SKIP = 8;
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
const PASSPHRASE_FEEDBACK_DELAY = 500; // How long after keystroke to offer passphrase feedback, ms.
// XXX: copied from ShareDialog: factor out into utils
function selectText(target) {
const range = document.createRange();
range.selectNodeContents(target);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
/*
* Walks the user through the process of creating a passphrase to guard Secure
* Secret Storage in account data.
@ -169,8 +160,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
}
_onCopyClick = () => {
selectText(this._recoveryKeyNode);
const successful = document.execCommand('copy');
const successful = copyNode(this._recoveryKeyNode);
if (successful) {
this.setState({
copied: true,
@ -472,7 +462,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
let helpText;
if (this.state.zxcvbnResult) {
if (this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE) {
helpText = _t("Great! This passphrase looks strong enough.");
helpText = _t("Great! This recovery passphrase looks strong enough.");
} else {
// We take the warning from zxcvbn or failing that, the first
// suggestion. In practice The first is generally the most relevant
@ -497,12 +487,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
return <form onSubmit={this._onPassPhraseNextClick}>
<p>{_t(
"Set up encryption on this session to allow it to verify other sessions, " +
"granting them access to encrypted messages and marking them as trusted for other users.",
)}</p>
<p>{_t(
"Secure your encryption keys with a passphrase. For maximum security " +
"this should be different to your account password:",
"Set a recovery passphrase to secure encrypted information and recover it if you log out. " +
"This should be different to your account password:",
)}</p>
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
@ -511,7 +497,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
className="mx_CreateSecretStorageDialog_passPhraseField"
onChange={this._onPassPhraseChange}
value={this.state.passPhrase}
label={_t("Enter a passphrase")}
label={_t("Enter a recovery passphrase")}
autoFocus={true}
autoComplete="new-password"
/>
@ -522,7 +508,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
</div>
<LabelledToggleSwitch
label={ _t("Back up my encryption keys, securing them with the same passphrase")}
label={ _t("Back up encrypted message keys")}
onChange={this._onUseKeyBackupChange} value={this.state.useKeyBackup}
/>
@ -579,7 +565,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
<p>{_t(
"Enter your passphrase a second time to confirm it.",
"Enter your recovery passphrase a second time to confirm it.",
)}</p>
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
<Field
@ -587,7 +573,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
onChange={this._onPassPhraseConfirmChange}
value={this.state.passPhraseConfirm}
className="mx_CreateSecretStorageDialog_passPhraseField"
label={_t("Confirm your passphrase")}
label={_t("Confirm your recovery passphrase")}
autoFocus={true}
autoComplete="new-password"
/>
@ -614,7 +600,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
return <div>
<p>{_t(
"Your recovery key is a safety net - you can use it to restore " +
"access to your encrypted messages if you forget your passphrase.",
"access to your encrypted messages if you forget your recovery passphrase.",
)}</p>
<p>{_t(
"Keep a copy of it somewhere secure, like a password manager or even a safe.",
@ -713,7 +699,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
case PHASE_PASSPHRASE:
return _t('Set up encryption');
case PHASE_PASSPHRASE_CONFIRM:
return _t('Confirm passphrase');
return _t('Confirm recovery passphrase');
case PHASE_CONFIRM_SKIP:
return _t('Are you sure?');
case PHASE_SHOWKEY:

View file

@ -59,17 +59,17 @@ export default class CompleteSecurity extends React.Component {
let title;
if (phase === PHASE_INTRO) {
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
title = _t("Complete security");
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
title = _t("Verify this session");
} else if (phase === PHASE_DONE) {
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_verified"></span>;
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_verified" />;
title = _t("Session verified");
} else if (phase === PHASE_CONFIRM_SKIP) {
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
title = _t("Are you sure?");
} else if (phase === PHASE_BUSY) {
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
title = _t("Complete security");
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
title = _t("Verify this session");
} else {
throw new Error(`Unknown phase ${phase}`);
}

View file

@ -116,7 +116,7 @@ export default class SetupEncryptionBody extends React.Component {
"granting it access to encrypted messages.",
)}</p>
<p>{_t(
"If you cant access one, <button>use your recovery key or passphrase.</button>",
"If you cant access one, <button>use your recovery key or recovery passphrase.</button>",
{}, {
button: sub => <AccessibleButton element="span"
className="mx_linkButton"

View file

@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2020 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,15 +15,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import {Room, User, Group, RoomMember, MatrixEvent} from 'matrix-js-sdk';
import * as React from 'react';
import * as PropTypes from 'prop-types';
import {Room} from "matrix-js-sdk/src/models/room";
import {User} from "matrix-js-sdk/src/models/user";
import {Group} from "matrix-js-sdk/src/models/group";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import QRCode from 'qrcode-react';
import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import * as ContextMenu from "../../structures/ContextMenu";
import {toRightOf} from "../../structures/ContextMenu";
import {copyPlaintext, selectText} from "../../../utils/strings";
const socials = [
{
@ -52,7 +58,18 @@ const socials = [
},
];
export default class ShareDialog extends React.Component {
interface IProps {
onFinished: () => void;
target: Room | User | Group | RoomMember | MatrixEvent;
permalinkCreator: RoomPermalinkCreator;
}
interface IState {
linkSpecificEvent: boolean;
permalinkCreator: RoomPermalinkCreator;
}
export default class ShareDialog extends React.PureComponent<IProps, IState> {
static propTypes = {
onFinished: PropTypes.func.isRequired,
target: PropTypes.oneOfType([
@ -64,6 +81,8 @@ export default class ShareDialog extends React.Component {
]).isRequired,
};
protected closeCopiedTooltip: () => void;
constructor(props) {
super(props);
@ -81,45 +100,26 @@ export default class ShareDialog extends React.Component {
linkSpecificEvent: this.props.target instanceof MatrixEvent,
permalinkCreator,
};
this._link = createRef();
}
static _selectText(target) {
const range = document.createRange();
range.selectNodeContents(target);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
static onLinkClick(e) {
e.preventDefault();
const {target} = e;
ShareDialog._selectText(target);
selectText(e.target);
}
onCopyClick(e) {
async onCopyClick(e) {
e.preventDefault();
const target = e.target; // copy target before we go async and React throws it away
ShareDialog._selectText(this._link.current);
let successful;
try {
successful = document.execCommand('copy');
} catch (err) {
console.error('Failed to copy: ', err);
}
const buttonRect = e.target.getBoundingClientRect();
const successful = await copyPlaintext(this.getUrl());
const buttonRect = target.getBoundingClientRect();
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 2),
message: successful ? _t('Copied!') : _t('Failed to copy'),
});
// Drop a reference to this close handler for componentWillUnmount
this.closeCopiedTooltip = e.target.onmouseleave = close;
this.closeCopiedTooltip = target.onmouseleave = close;
}
onLinkSpecificEventCheckboxClick() {
@ -134,10 +134,32 @@ export default class ShareDialog extends React.Component {
if (this.closeCopiedTooltip) this.closeCopiedTooltip();
}
render() {
let title;
getUrl() {
let matrixToUrl;
if (this.props.target instanceof Room) {
if (this.state.linkSpecificEvent) {
const events = this.props.target.getLiveTimeline().getEvents();
matrixToUrl = this.state.permalinkCreator.forEvent(events[events.length - 1].getId());
} else {
matrixToUrl = this.state.permalinkCreator.forRoom();
}
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
matrixToUrl = makeUserPermalink(this.props.target.userId);
} else if (this.props.target instanceof Group) {
matrixToUrl = makeGroupPermalink(this.props.target.groupId);
} else if (this.props.target instanceof MatrixEvent) {
if (this.state.linkSpecificEvent) {
matrixToUrl = this.props.permalinkCreator.forEvent(this.props.target.getId());
} else {
matrixToUrl = this.props.permalinkCreator.forRoom();
}
}
return matrixToUrl;
}
render() {
let title;
let checkbox;
if (this.props.target instanceof Room) {
@ -155,18 +177,10 @@ export default class ShareDialog extends React.Component {
</label>
</div>;
}
if (this.state.linkSpecificEvent) {
matrixToUrl = this.state.permalinkCreator.forEvent(events[events.length - 1].getId());
} else {
matrixToUrl = this.state.permalinkCreator.forRoom();
}
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
title = _t('Share User');
matrixToUrl = makeUserPermalink(this.props.target.userId);
} else if (this.props.target instanceof Group) {
title = _t('Share Community');
matrixToUrl = makeGroupPermalink(this.props.target.groupId);
} else if (this.props.target instanceof MatrixEvent) {
title = _t('Share Room Message');
checkbox = <div>
@ -178,14 +192,9 @@ export default class ShareDialog extends React.Component {
{ _t('Link to selected message') }
</label>
</div>;
if (this.state.linkSpecificEvent) {
matrixToUrl = this.props.permalinkCreator.forEvent(this.props.target.getId());
} else {
matrixToUrl = this.props.permalinkCreator.forRoom();
}
}
const matrixToUrl = this.getUrl();
const encodedUrl = encodeURIComponent(matrixToUrl);
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
@ -196,8 +205,7 @@ export default class ShareDialog extends React.Component {
>
<div className="mx_ShareDialog_content">
<div className="mx_ShareDialog_matrixto">
<a ref={this._link}
href={matrixToUrl}
<a href={matrixToUrl}
onClick={ShareDialog.onLinkClick}
className="mx_ShareDialog_matrixto_link"
>
@ -216,17 +224,18 @@ export default class ShareDialog extends React.Component {
<QRCode value={matrixToUrl} size={256} logoWidth={48} logo={require("../../../../res/img/matrix-m.svg")} />
</div>
<div className="mx_ShareDialog_social_container">
{
socials.map((social) => <a rel="noreferrer noopener"
target="_blank"
key={social.name}
name={social.name}
href={social.url(encodedUrl)}
className="mx_ShareDialog_social_icon"
{ socials.map((social) => (
<a
rel="noreferrer noopener"
target="_blank"
key={social.name}
title={social.name}
href={social.url(encodedUrl)}
className="mx_ShareDialog_social_icon"
>
<img src={social.img} alt={social.name} height={64} width={64} />
</a>)
}
</a>
)) }
</div>
</div>
</div>

View file

@ -48,7 +48,7 @@ export default class VerificationRequestDialog extends React.Component {
const member = this.props.member ||
otherUserId && MatrixClientPeg.get().getUser(otherUserId);
const title = request && request.isSelfVerification ?
_t("Verify this session") : _t("Verification Request");
_t("Verify other session") : _t("Verification Request");
return <BaseDialog className="mx_InfoDialog" onFinished={this.onFinished}
contentId="mx_Dialog_content"

View file

@ -283,7 +283,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
title = _t("Recovery key mismatch");
content = <div>
<p>{_t(
"Backup could not be decrypted with this key: " +
"Backup could not be decrypted with this recovery key: " +
"please verify that you entered the correct recovery key.",
)}</p>
</div>;
@ -291,7 +291,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
title = _t("Incorrect recovery passphrase");
content = <div>
<p>{_t(
"Backup could not be decrypted with this passphrase: " +
"Backup could not be decrypted with this recovery passphrase: " +
"please verify that you entered the correct recovery passphrase.",
)}</p>
</div>;

View file

@ -119,14 +119,14 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
if (hasPassphrase && !this.state.forceRecoveryKey) {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
title = _t("Enter secret storage passphrase");
title = _t("Enter recovery passphrase");
let keyStatus;
if (this.state.keyMatches === false) {
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
{"\uD83D\uDC4E "}{_t(
"Unable to access secret storage. Please verify that you " +
"entered the correct passphrase.",
"Unable to access secret storage. " +
"Please verify that you entered the correct recovery passphrase.",
)}
</div>;
} else {
@ -135,13 +135,12 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
content = <div>
<p>{_t(
"<b>Warning</b>: You should only access secret storage " +
"from a trusted computer.", {},
"<b>Warning</b>: You should only do this on a trusted computer.", {},
{ b: sub => <b>{sub}</b> },
)}</p>
<p>{_t(
"Access your secure message history and your cross-signing " +
"identity for verifying other sessions by entering your passphrase.",
"identity for verifying other sessions by entering your recovery passphrase.",
)}</p>
<form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this._onPassPhraseNext}>
@ -164,7 +163,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
/>
</form>
{_t(
"If you've forgotten your passphrase you can "+
"If you've forgotten your recovery passphrase you can "+
"<button1>use your recovery key</button1> or " +
"<button2>set up new recovery options</button2>."
, {}, {
@ -183,7 +182,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
})}
</div>;
} else {
title = _t("Enter secret storage recovery key");
title = _t("Enter recovery key");
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@ -193,8 +192,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
} else if (this.state.keyMatches === false) {
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
{"\uD83D\uDC4E "}{_t(
"Unable to access secret storage. Please verify that you " +
"entered the correct recovery key.",
"Unable to access secret storage. " +
"Please verify that you entered the correct recovery key.",
)}
</div>;
} else if (this.state.recoveryKeyValid) {
@ -209,8 +208,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
content = <div>
<p>{_t(
"<b>Warning</b>: You should only access secret storage " +
"from a trusted computer.", {},
"<b>Warning</b>: You should only do this on a trusted computer.", {},
{ b: sub => <b>{sub}</b> },
)}</p>
<p>{_t(

View file

@ -34,6 +34,7 @@ import {pillifyLinks, unmountPills} from '../../../utils/pillify';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
import {toRightOf} from "../../structures/ContextMenu";
import {copyPlaintext} from "../../../utils/strings";
export default createReactClass({
displayName: 'TextualBody',
@ -69,23 +70,6 @@ export default createReactClass({
};
},
copyToClipboard: function(text) {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
let successful = false;
try {
successful = document.execCommand('copy');
} catch (err) {
console.log('Unable to copy');
}
document.body.removeChild(textArea);
return successful;
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._content = createRef();
@ -277,17 +261,17 @@ export default createReactClass({
Array.from(ReactDOM.findDOMNode(this).querySelectorAll('.mx_EventTile_body pre')).forEach((p) => {
const button = document.createElement("span");
button.className = "mx_EventTile_copyButton";
button.onclick = (e) => {
button.onclick = async () => {
const copyCode = button.parentNode.getElementsByTagName("pre")[0];
const successful = this.copyToClipboard(copyCode.textContent);
const successful = await copyPlaintext(copyCode.textContent);
const buttonRect = e.target.getBoundingClientRect();
const buttonRect = button.getBoundingClientRect();
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 2),
message: successful ? _t('Copied!') : _t('Failed to copy'),
});
e.target.onmouseleave = close;
button.onmouseleave = close;
};
// Wrap a div around <pre> so that the copy button can be correctly positioned

View file

@ -18,7 +18,9 @@ import React from 'react';
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import {_t} from "../../../languageHandler";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.room_settings.RoomPublishSetting")
export default class RoomPublishSetting extends React.PureComponent {
constructor(props) {
super(props);

View file

@ -20,6 +20,7 @@ import { _t, _td } from '../../../languageHandler';
import {PendingActionSpinner} from "../right_panel/EncryptionInfo";
import AccessibleButton from "../elements/AccessibleButton";
import DialogButtons from "../elements/DialogButtons";
import { fixupColorFonts } from '../../../utils/FontManager';
function capFirst(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
@ -44,6 +45,13 @@ export default class VerificationShowSas extends React.Component {
};
}
componentWillMount() {
// As this component is also used before login (during complete security),
// also make sure we have a working emoji font to display the SAS emojis here.
// This is also done from LoggedInView.
fixupColorFonts();
}
onMatchClick = () => {
this.setState({ pending: true });
this.props.onDone();

View file

@ -70,7 +70,7 @@
"Failure to create room": "Failure to create room",
"If you cancel now, you won't complete verifying the other user.": "If you cancel now, you won't complete verifying the other user.",
"If you cancel now, you won't complete verifying your other session.": "If you cancel now, you won't complete verifying your other session.",
"If you cancel now, you won't complete your secret storage operation.": "If you cancel now, you won't complete your secret storage operation.",
"If you cancel now, you won't complete your operation.": "If you cancel now, you won't complete your operation.",
"Cancel entering passphrase?": "Cancel entering passphrase?",
"Enter passphrase": "Enter passphrase",
"Cancel": "Cancel",
@ -444,7 +444,7 @@
"Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)",
"Show previews/thumbnails for images": "Show previews/thumbnails for images",
"Enable message search in encrypted rooms": "Enable message search in encrypted rooms",
"Keep secret storage passphrase in memory for this session": "Keep secret storage passphrase in memory for this session",
"Keep recovery passphrase in memory for this session": "Keep recovery passphrase in memory for this session",
"How fast should messages be downloaded.": "How fast should messages be downloaded.",
"Manually verify all remote sessions": "Manually verify all remote sessions",
"Collecting app version information": "Collecting app version information",
@ -1772,18 +1772,19 @@
"Upload %(count)s other files|one": "Upload %(count)s other file",
"Cancel All": "Cancel All",
"Upload Error": "Upload Error",
"Verify other session": "Verify other session",
"Verification Request": "Verification Request",
"A widget would like to verify your identity": "A widget would like to verify your identity",
"A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.",
"Remember my selection for this widget": "Remember my selection for this widget",
"Allow": "Allow",
"Deny": "Deny",
"Enter secret storage passphrase": "Enter secret storage passphrase",
"Unable to access secret storage. Please verify that you entered the correct passphrase.": "Unable to access secret storage. Please verify that you entered the correct passphrase.",
"<b>Warning</b>: You should only access secret storage from a trusted computer.": "<b>Warning</b>: You should only access secret storage from a trusted computer.",
"Access your secure message history and your cross-signing identity for verifying other sessions by entering your passphrase.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your passphrase.",
"If you've forgotten your passphrase you can <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>.": "If you've forgotten your passphrase you can <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>.",
"Enter secret storage recovery key": "Enter secret storage recovery key",
"Enter recovery passphrase": "Enter recovery passphrase",
"Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.",
"<b>Warning</b>: You should only do this on a trusted computer.": "<b>Warning</b>: You should only do this on a trusted computer.",
"Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.",
"If you've forgotten your recovery passphrase you can <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>.": "If you've forgotten your recovery passphrase you can <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>.",
"Enter recovery key": "Enter recovery key",
"Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.",
"This looks like a valid recovery key!": "This looks like a valid recovery key!",
"Not a valid recovery key": "Not a valid recovery key",
@ -1791,19 +1792,17 @@
"If you've forgotten your recovery key you can <button>set up new recovery options</button>.": "If you've forgotten your recovery key you can <button>set up new recovery options</button>.",
"Unable to load backup status": "Unable to load backup status",
"Recovery key mismatch": "Recovery key mismatch",
"Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.",
"Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.",
"Incorrect recovery passphrase": "Incorrect recovery passphrase",
"Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.",
"Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.",
"Unable to restore backup": "Unable to restore backup",
"No backup found!": "No backup found!",
"Backup restored": "Backup restored",
"Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!",
"Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys",
"Enter recovery passphrase": "Enter recovery passphrase",
"<b>Warning</b>: you should only set up key backup from a trusted computer.": "<b>Warning</b>: you should only set up key backup from a trusted computer.",
"Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.",
"If you've forgotten your recovery passphrase you can <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>": "If you've forgotten your recovery passphrase you can <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>",
"Enter recovery key": "Enter recovery key",
"<b>Warning</b>: You should only set up key backup from a trusted computer.": "<b>Warning</b>: You should only set up key backup from a trusted computer.",
"Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.",
"If you've forgotten your recovery key you can <button>set up new recovery options</button>": "If you've forgotten your recovery key you can <button>set up new recovery options</button>",
@ -2064,7 +2063,6 @@
"Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s",
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
"Could not load user profile": "Could not load user profile",
"Complete security": "Complete security",
"Session verified": "Session verified",
"Failed to send email": "Failed to send email",
"The email address linked to your account must be entered.": "The email address linked to your account must be entered.",
@ -2119,7 +2117,7 @@
"Registration Successful": "Registration Successful",
"Create your account": "Create your account",
"Use an existing session to verify this one, granting it access to encrypted messages.": "Use an existing session to verify this one, granting it access to encrypted messages.",
"If you cant access one, <button>use your recovery key or passphrase.</button>": "If you cant access one, <button>use your recovery key or passphrase.</button>",
"If you cant access one, <button>use your recovery key or recovery passphrase.</button>": "If you cant access one, <button>use your recovery key or recovery passphrase.</button>",
"Use your other device to continue…": "Use your other device to continue…",
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
"Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.",
@ -2182,18 +2180,17 @@
"Restore": "Restore",
"You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.",
"Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
"Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.",
"Set up encryption on this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Set up encryption on this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
"Secure your encryption keys with a passphrase. For maximum security this should be different to your account password:": "Secure your encryption keys with a passphrase. For maximum security this should be different to your account password:",
"Enter a passphrase": "Enter a passphrase",
"Back up my encryption keys, securing them with the same passphrase": "Back up my encryption keys, securing them with the same passphrase",
"Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.",
"Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:": "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:",
"Enter a recovery passphrase": "Enter a recovery passphrase",
"Back up encrypted message keys": "Back up encrypted message keys",
"Set up with a recovery key": "Set up with a recovery key",
"That matches!": "That matches!",
"That doesn't match.": "That doesn't match.",
"Go back to set it again.": "Go back to set it again.",
"Enter your passphrase a second time to confirm it.": "Enter your passphrase a second time to confirm it.",
"Confirm your passphrase": "Confirm your passphrase",
"Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.",
"Enter your recovery passphrase a second time to confirm it.": "Enter your recovery passphrase a second time to confirm it.",
"Confirm your recovery passphrase": "Confirm your recovery passphrase",
"Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.",
"Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.",
"Your recovery key": "Your recovery key",
"Copy": "Copy",
@ -2205,19 +2202,20 @@
"<b>Copy it</b> to your personal cloud storage": "<b>Copy it</b> to your personal cloud storage",
"You can now verify your other devices, and other users to keep your chats safe.": "You can now verify your other devices, and other users to keep your chats safe.",
"Upgrade your encryption": "Upgrade your encryption",
"Confirm recovery passphrase": "Confirm recovery passphrase",
"Make a copy of your recovery key": "Make a copy of your recovery key",
"You're done!": "You're done!",
"Unable to set up secret storage": "Unable to set up secret storage",
"Retry": "Retry",
"We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.",
"We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.",
"For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.",
"Enter a passphrase...": "Enter a passphrase...",
"Please enter your passphrase a second time to confirm.": "Please enter your passphrase a second time to confirm.",
"Repeat your passphrase...": "Repeat your passphrase...",
"Enter a recovery passphrase...": "Enter a recovery passphrase...",
"Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.",
"Repeat your recovery passphrase...": "Repeat your recovery passphrase...",
"Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).",
"Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.",
"Set up Secure Message Recovery": "Set up Secure Message Recovery",
"Secure your backup with a passphrase": "Secure your backup with a passphrase",
"Secure your backup with a recovery passphrase": "Secure your backup with a recovery passphrase",
"Starting backup...": "Starting backup...",
"Success!": "Success!",
"Create key backup": "Create key backup",

View file

@ -516,7 +516,7 @@ export const SETTINGS = {
},
"keepSecretStoragePassphraseForSession": {
supportedLevels: ['device', 'config'],
displayName: _td("Keep secret storage passphrase in memory for this session"),
displayName: _td("Keep recovery passphrase in memory for this session"),
default: false,
},
"crawlerSleepTime": {

75
src/utils/strings.ts Normal file
View file

@ -0,0 +1,75 @@
/*
Copyright 2020 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.
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.
*/
/**
* Copy plaintext to user's clipboard
* It will overwrite user's selection range
* In certain browsers it may only work if triggered by a user action or may ask user for permissions
* Tries to use new async clipboard API if available
* @param text the plaintext to put in the user's clipboard
*/
export async function copyPlaintext(text: string): Promise<boolean> {
try {
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
return true;
} else {
const textArea = document.createElement("textarea");
textArea.value = text;
// Avoid scrolling to bottom
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
const selection = document.getSelection();
const range = document.createRange();
// range.selectNodeContents(textArea);
range.selectNode(textArea);
selection.removeAllRanges();
selection.addRange(range);
const successful = document.execCommand("copy");
selection.removeAllRanges();
document.body.removeChild(textArea);
return successful;
}
} catch (e) {
console.error("copyPlaintext failed", e);
}
return false;
}
export function selectText(target: Element) {
const range = document.createRange();
range.selectNodeContents(target);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
/**
* Copy rich text to user's clipboard
* It will overwrite user's selection range
* In certain browsers it may only work if triggered by a user action or may ask user for permissions
* @param ref pointer to the node to copy
*/
export function copyNode(ref: Element): boolean {
selectText(ref);
return document.execCommand('copy');
}

View file

@ -100,7 +100,7 @@ describe("AccessSecretStorageDialog", function() {
});
expect(notification.props.children).toEqual(
["\uD83D\uDC4E ", "Unable to access secret storage. Please verify that you " +
"entered the correct passphrase."]);
"entered the correct recovery passphrase."]);
done();
});
});