Merge branch 'develop' into travis/cross-sign-setting
This commit is contained in:
commit
db749519e7
18 changed files with 226 additions and 176 deletions
|
@ -19,6 +19,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageContextMenu_field {
|
.mx_MessageContextMenu_field {
|
||||||
|
display: block;
|
||||||
padding: 3px 6px 3px 6px;
|
padding: 3px 6px 3px 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -51,7 +51,7 @@ async function confirmToDismiss(name) {
|
||||||
} else if (name === "m.cross_signing.self_signing") {
|
} else if (name === "m.cross_signing.self_signing") {
|
||||||
description = _t("If you cancel now, you won't complete verifying your other session.");
|
description = _t("If you cancel now, you won't complete verifying your other session.");
|
||||||
} else {
|
} 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");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { _t } from '../../../../languageHandler';
|
||||||
import { accessSecretStorage } from '../../../../CrossSigningManager';
|
import { accessSecretStorage } from '../../../../CrossSigningManager';
|
||||||
import SettingsStore from '../../../../settings/SettingsStore';
|
import SettingsStore from '../../../../settings/SettingsStore';
|
||||||
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
||||||
|
import {copyNode} from "../../../../utils/strings";
|
||||||
|
|
||||||
const PHASE_PASSPHRASE = 0;
|
const PHASE_PASSPHRASE = 0;
|
||||||
const PHASE_PASSPHRASE_CONFIRM = 1;
|
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 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.
|
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
|
* Walks the user through the process of creating an e2e key backup
|
||||||
* on the server.
|
* on the server.
|
||||||
|
@ -101,8 +92,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCopyClick = () => {
|
_onCopyClick = () => {
|
||||||
selectText(this._recoveryKeyNode);
|
const successful = copyNode(this._recoveryKeyNode);
|
||||||
const successful = document.execCommand('copy');
|
|
||||||
if (successful) {
|
if (successful) {
|
||||||
this.setState({
|
this.setState({
|
||||||
copied: true,
|
copied: true,
|
||||||
|
@ -272,7 +262,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
let helpText;
|
let helpText;
|
||||||
if (this.state.zxcvbnResult) {
|
if (this.state.zxcvbnResult) {
|
||||||
if (this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE) {
|
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 {
|
} else {
|
||||||
const suggestions = [];
|
const suggestions = [];
|
||||||
for (let i = 0; i < this.state.zxcvbnResult.feedback.suggestions.length; ++i) {
|
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>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"We'll store an encrypted copy of your keys on our server. " +
|
"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>
|
||||||
<p>{_t("For maximum security, this should be different from your account password.")}</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}
|
onChange={this._onPassPhraseChange}
|
||||||
value={this.state.passPhrase}
|
value={this.state.passPhrase}
|
||||||
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
||||||
placeholder={_t("Enter a passphrase...")}
|
placeholder={_t("Enter a recovery passphrase...")}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
<div className="mx_CreateKeyBackupDialog_passPhraseHelp">
|
<div className="mx_CreateKeyBackupDialog_passPhraseHelp">
|
||||||
|
@ -364,7 +354,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
|
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Please enter your passphrase a second time to confirm.",
|
"Please enter your recovery passphrase a second time to confirm.",
|
||||||
)}</p>
|
)}</p>
|
||||||
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||||
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
||||||
|
@ -373,7 +363,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
onChange={this._onPassPhraseConfirmChange}
|
onChange={this._onPassPhraseConfirmChange}
|
||||||
value={this.state.passPhraseConfirm}
|
value={this.state.passPhraseConfirm}
|
||||||
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
||||||
placeholder={_t("Repeat your passphrase...")}
|
placeholder={_t("Repeat your recovery passphrase...")}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -393,7 +383,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
return <div>
|
return <div>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Your recovery key is a safety net - you can use it to restore " +
|
"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>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"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.",
|
||||||
|
@ -487,9 +477,9 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
_titleForPhase(phase) {
|
_titleForPhase(phase) {
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case PHASE_PASSPHRASE:
|
case PHASE_PASSPHRASE:
|
||||||
return _t('Secure your backup with a passphrase');
|
return _t('Secure your backup with a recovery passphrase');
|
||||||
case PHASE_PASSPHRASE_CONFIRM:
|
case PHASE_PASSPHRASE_CONFIRM:
|
||||||
return _t('Confirm your passphrase');
|
return _t('Confirm your recovery passphrase');
|
||||||
case PHASE_OPTOUT_CONFIRM:
|
case PHASE_OPTOUT_CONFIRM:
|
||||||
return _t('Warning!');
|
return _t('Warning!');
|
||||||
case PHASE_SHOWKEY:
|
case PHASE_SHOWKEY:
|
||||||
|
|
|
@ -57,8 +57,7 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
|
||||||
</span>;
|
</span>;
|
||||||
|
|
||||||
const newMethodDetected = <p>{_t(
|
const newMethodDetected = <p>{_t(
|
||||||
"A new recovery passphrase and key for Secure " +
|
"A new recovery passphrase and key for Secure Messages have been detected.",
|
||||||
"Messages have been detected.",
|
|
||||||
)}</p>;
|
)}</p>;
|
||||||
|
|
||||||
const hackWarning = <p className="warning">{_t(
|
const hackWarning = <p className="warning">{_t(
|
||||||
|
|
|
@ -24,6 +24,7 @@ import FileSaver from 'file-saver';
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
import Modal from '../../../../Modal';
|
import Modal from '../../../../Modal';
|
||||||
import { promptForBackupPassphrase } from '../../../../CrossSigningManager';
|
import { promptForBackupPassphrase } from '../../../../CrossSigningManager';
|
||||||
|
import {copyNode} from "../../../../utils/strings";
|
||||||
|
|
||||||
const PHASE_LOADING = 0;
|
const PHASE_LOADING = 0;
|
||||||
const PHASE_MIGRATE = 1;
|
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 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.
|
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
|
* Walks the user through the process of creating a passphrase to guard Secure
|
||||||
* Secret Storage in account data.
|
* Secret Storage in account data.
|
||||||
|
@ -169,8 +160,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCopyClick = () => {
|
_onCopyClick = () => {
|
||||||
selectText(this._recoveryKeyNode);
|
const successful = copyNode(this._recoveryKeyNode);
|
||||||
const successful = document.execCommand('copy');
|
|
||||||
if (successful) {
|
if (successful) {
|
||||||
this.setState({
|
this.setState({
|
||||||
copied: true,
|
copied: true,
|
||||||
|
@ -472,7 +462,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
let helpText;
|
let helpText;
|
||||||
if (this.state.zxcvbnResult) {
|
if (this.state.zxcvbnResult) {
|
||||||
if (this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE) {
|
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 {
|
} else {
|
||||||
// We take the warning from zxcvbn or failing that, the first
|
// We take the warning from zxcvbn or failing that, the first
|
||||||
// suggestion. In practice The first is generally the most relevant
|
// 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}>
|
return <form onSubmit={this._onPassPhraseNextClick}>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Set up encryption on this session to allow it to verify other sessions, " +
|
"Set a recovery passphrase to secure encrypted information and recover it if you log out. " +
|
||||||
"granting them access to encrypted messages and marking them as trusted for other users.",
|
"This should be different to your account password:",
|
||||||
)}</p>
|
|
||||||
<p>{_t(
|
|
||||||
"Secure your encryption keys with a passphrase. For maximum security " +
|
|
||||||
"this should be different to your account password:",
|
|
||||||
)}</p>
|
)}</p>
|
||||||
|
|
||||||
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
||||||
|
@ -511,7 +497,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
className="mx_CreateSecretStorageDialog_passPhraseField"
|
className="mx_CreateSecretStorageDialog_passPhraseField"
|
||||||
onChange={this._onPassPhraseChange}
|
onChange={this._onPassPhraseChange}
|
||||||
value={this.state.passPhrase}
|
value={this.state.passPhrase}
|
||||||
label={_t("Enter a passphrase")}
|
label={_t("Enter a recovery passphrase")}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
|
@ -522,7 +508,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<LabelledToggleSwitch
|
<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}
|
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');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
|
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Enter your passphrase a second time to confirm it.",
|
"Enter your recovery passphrase a second time to confirm it.",
|
||||||
)}</p>
|
)}</p>
|
||||||
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
||||||
<Field
|
<Field
|
||||||
|
@ -587,7 +573,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
onChange={this._onPassPhraseConfirmChange}
|
onChange={this._onPassPhraseConfirmChange}
|
||||||
value={this.state.passPhraseConfirm}
|
value={this.state.passPhraseConfirm}
|
||||||
className="mx_CreateSecretStorageDialog_passPhraseField"
|
className="mx_CreateSecretStorageDialog_passPhraseField"
|
||||||
label={_t("Confirm your passphrase")}
|
label={_t("Confirm your recovery passphrase")}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
|
@ -614,7 +600,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
return <div>
|
return <div>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Your recovery key is a safety net - you can use it to restore " +
|
"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>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"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.",
|
||||||
|
@ -713,7 +699,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
case PHASE_PASSPHRASE:
|
case PHASE_PASSPHRASE:
|
||||||
return _t('Set up encryption');
|
return _t('Set up encryption');
|
||||||
case PHASE_PASSPHRASE_CONFIRM:
|
case PHASE_PASSPHRASE_CONFIRM:
|
||||||
return _t('Confirm passphrase');
|
return _t('Confirm recovery passphrase');
|
||||||
case PHASE_CONFIRM_SKIP:
|
case PHASE_CONFIRM_SKIP:
|
||||||
return _t('Are you sure?');
|
return _t('Are you sure?');
|
||||||
case PHASE_SHOWKEY:
|
case PHASE_SHOWKEY:
|
||||||
|
|
|
@ -59,17 +59,17 @@ export default class CompleteSecurity extends React.Component {
|
||||||
let title;
|
let title;
|
||||||
|
|
||||||
if (phase === PHASE_INTRO) {
|
if (phase === PHASE_INTRO) {
|
||||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
|
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
|
||||||
title = _t("Complete security");
|
title = _t("Verify this session");
|
||||||
} else if (phase === PHASE_DONE) {
|
} 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");
|
title = _t("Session verified");
|
||||||
} else if (phase === PHASE_CONFIRM_SKIP) {
|
} 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?");
|
title = _t("Are you sure?");
|
||||||
} else if (phase === PHASE_BUSY) {
|
} else if (phase === PHASE_BUSY) {
|
||||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
|
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
|
||||||
title = _t("Complete security");
|
title = _t("Verify this session");
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unknown phase ${phase}`);
|
throw new Error(`Unknown phase ${phase}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
"granting it access to encrypted messages.",
|
"granting it access to encrypted messages.",
|
||||||
)}</p>
|
)}</p>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"If you can’t access one, <button>use your recovery key or passphrase.</button>",
|
"If you can’t access one, <button>use your recovery key or recovery passphrase.</button>",
|
||||||
{}, {
|
{}, {
|
||||||
button: sub => <AccessibleButton element="span"
|
button: sub => <AccessibleButton element="span"
|
||||||
className="mx_linkButton"
|
className="mx_linkButton"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {createRef} from 'react';
|
import * as React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import * as PropTypes from 'prop-types';
|
||||||
import {Room, User, Group, RoomMember, MatrixEvent} from 'matrix-js-sdk';
|
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 * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import QRCode from 'qrcode-react';
|
import QRCode from 'qrcode-react';
|
||||||
import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
|
import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
|
||||||
import * as ContextMenu from "../../structures/ContextMenu";
|
import * as ContextMenu from "../../structures/ContextMenu";
|
||||||
import {toRightOf} from "../../structures/ContextMenu";
|
import {toRightOf} from "../../structures/ContextMenu";
|
||||||
|
import {copyPlaintext, selectText} from "../../../utils/strings";
|
||||||
|
|
||||||
const socials = [
|
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 = {
|
static propTypes = {
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
target: PropTypes.oneOfType([
|
target: PropTypes.oneOfType([
|
||||||
|
@ -64,6 +81,8 @@ export default class ShareDialog extends React.Component {
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected closeCopiedTooltip: () => void;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -81,45 +100,26 @@ export default class ShareDialog extends React.Component {
|
||||||
linkSpecificEvent: this.props.target instanceof MatrixEvent,
|
linkSpecificEvent: this.props.target instanceof MatrixEvent,
|
||||||
permalinkCreator,
|
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) {
|
static onLinkClick(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const {target} = e;
|
selectText(e.target);
|
||||||
ShareDialog._selectText(target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCopyClick(e) {
|
async onCopyClick(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
const target = e.target; // copy target before we go async and React throws it away
|
||||||
|
|
||||||
ShareDialog._selectText(this._link.current);
|
const successful = await copyPlaintext(this.getUrl());
|
||||||
|
const buttonRect = target.getBoundingClientRect();
|
||||||
let successful;
|
|
||||||
try {
|
|
||||||
successful = document.execCommand('copy');
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to copy: ', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
const buttonRect = e.target.getBoundingClientRect();
|
|
||||||
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
|
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
|
||||||
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
|
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
|
||||||
...toRightOf(buttonRect, 2),
|
...toRightOf(buttonRect, 2),
|
||||||
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
||||||
});
|
});
|
||||||
// Drop a reference to this close handler for componentWillUnmount
|
// Drop a reference to this close handler for componentWillUnmount
|
||||||
this.closeCopiedTooltip = e.target.onmouseleave = close;
|
this.closeCopiedTooltip = target.onmouseleave = close;
|
||||||
}
|
}
|
||||||
|
|
||||||
onLinkSpecificEventCheckboxClick() {
|
onLinkSpecificEventCheckboxClick() {
|
||||||
|
@ -134,10 +134,32 @@ export default class ShareDialog extends React.Component {
|
||||||
if (this.closeCopiedTooltip) this.closeCopiedTooltip();
|
if (this.closeCopiedTooltip) this.closeCopiedTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
getUrl() {
|
||||||
let title;
|
|
||||||
let matrixToUrl;
|
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;
|
let checkbox;
|
||||||
|
|
||||||
if (this.props.target instanceof Room) {
|
if (this.props.target instanceof Room) {
|
||||||
|
@ -155,18 +177,10 @@ export default class ShareDialog extends React.Component {
|
||||||
</label>
|
</label>
|
||||||
</div>;
|
</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) {
|
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
|
||||||
title = _t('Share User');
|
title = _t('Share User');
|
||||||
matrixToUrl = makeUserPermalink(this.props.target.userId);
|
|
||||||
} else if (this.props.target instanceof Group) {
|
} else if (this.props.target instanceof Group) {
|
||||||
title = _t('Share Community');
|
title = _t('Share Community');
|
||||||
matrixToUrl = makeGroupPermalink(this.props.target.groupId);
|
|
||||||
} else if (this.props.target instanceof MatrixEvent) {
|
} else if (this.props.target instanceof MatrixEvent) {
|
||||||
title = _t('Share Room Message');
|
title = _t('Share Room Message');
|
||||||
checkbox = <div>
|
checkbox = <div>
|
||||||
|
@ -178,14 +192,9 @@ export default class ShareDialog extends React.Component {
|
||||||
{ _t('Link to selected message') }
|
{ _t('Link to selected message') }
|
||||||
</label>
|
</label>
|
||||||
</div>;
|
</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 encodedUrl = encodeURIComponent(matrixToUrl);
|
||||||
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
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_content">
|
||||||
<div className="mx_ShareDialog_matrixto">
|
<div className="mx_ShareDialog_matrixto">
|
||||||
<a ref={this._link}
|
<a href={matrixToUrl}
|
||||||
href={matrixToUrl}
|
|
||||||
onClick={ShareDialog.onLinkClick}
|
onClick={ShareDialog.onLinkClick}
|
||||||
className="mx_ShareDialog_matrixto_link"
|
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")} />
|
<QRCode value={matrixToUrl} size={256} logoWidth={48} logo={require("../../../../res/img/matrix-m.svg")} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_ShareDialog_social_container">
|
<div className="mx_ShareDialog_social_container">
|
||||||
{
|
{ socials.map((social) => (
|
||||||
socials.map((social) => <a rel="noreferrer noopener"
|
<a
|
||||||
target="_blank"
|
rel="noreferrer noopener"
|
||||||
key={social.name}
|
target="_blank"
|
||||||
name={social.name}
|
key={social.name}
|
||||||
href={social.url(encodedUrl)}
|
title={social.name}
|
||||||
className="mx_ShareDialog_social_icon"
|
href={social.url(encodedUrl)}
|
||||||
|
className="mx_ShareDialog_social_icon"
|
||||||
>
|
>
|
||||||
<img src={social.img} alt={social.name} height={64} width={64} />
|
<img src={social.img} alt={social.name} height={64} width={64} />
|
||||||
</a>)
|
</a>
|
||||||
}
|
)) }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -48,7 +48,7 @@ export default class VerificationRequestDialog extends React.Component {
|
||||||
const member = this.props.member ||
|
const member = this.props.member ||
|
||||||
otherUserId && MatrixClientPeg.get().getUser(otherUserId);
|
otherUserId && MatrixClientPeg.get().getUser(otherUserId);
|
||||||
const title = request && request.isSelfVerification ?
|
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}
|
return <BaseDialog className="mx_InfoDialog" onFinished={this.onFinished}
|
||||||
contentId="mx_Dialog_content"
|
contentId="mx_Dialog_content"
|
||||||
|
|
|
@ -283,7 +283,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||||
title = _t("Recovery key mismatch");
|
title = _t("Recovery key mismatch");
|
||||||
content = <div>
|
content = <div>
|
||||||
<p>{_t(
|
<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.",
|
"please verify that you entered the correct recovery key.",
|
||||||
)}</p>
|
)}</p>
|
||||||
</div>;
|
</div>;
|
||||||
|
@ -291,7 +291,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||||
title = _t("Incorrect recovery passphrase");
|
title = _t("Incorrect recovery passphrase");
|
||||||
content = <div>
|
content = <div>
|
||||||
<p>{_t(
|
<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.",
|
"please verify that you entered the correct recovery passphrase.",
|
||||||
)}</p>
|
)}</p>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -119,14 +119,14 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
if (hasPassphrase && !this.state.forceRecoveryKey) {
|
if (hasPassphrase && !this.state.forceRecoveryKey) {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
title = _t("Enter secret storage passphrase");
|
title = _t("Enter recovery passphrase");
|
||||||
|
|
||||||
let keyStatus;
|
let keyStatus;
|
||||||
if (this.state.keyMatches === false) {
|
if (this.state.keyMatches === false) {
|
||||||
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
|
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
|
||||||
{"\uD83D\uDC4E "}{_t(
|
{"\uD83D\uDC4E "}{_t(
|
||||||
"Unable to access secret storage. Please verify that you " +
|
"Unable to access secret storage. " +
|
||||||
"entered the correct passphrase.",
|
"Please verify that you entered the correct recovery passphrase.",
|
||||||
)}
|
)}
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else {
|
||||||
|
@ -135,13 +135,12 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
|
|
||||||
content = <div>
|
content = <div>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"<b>Warning</b>: You should only access secret storage " +
|
"<b>Warning</b>: You should only do this on a trusted computer.", {},
|
||||||
"from a trusted computer.", {},
|
|
||||||
{ b: sub => <b>{sub}</b> },
|
{ b: sub => <b>{sub}</b> },
|
||||||
)}</p>
|
)}</p>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Access your secure message history and your cross-signing " +
|
"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>
|
)}</p>
|
||||||
|
|
||||||
<form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this._onPassPhraseNext}>
|
<form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this._onPassPhraseNext}>
|
||||||
|
@ -164,7 +163,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
{_t(
|
{_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 " +
|
"<button1>use your recovery key</button1> or " +
|
||||||
"<button2>set up new recovery options</button2>."
|
"<button2>set up new recovery options</button2>."
|
||||||
, {}, {
|
, {}, {
|
||||||
|
@ -183,7 +182,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
})}
|
})}
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else {
|
||||||
title = _t("Enter secret storage recovery key");
|
title = _t("Enter recovery key");
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
|
||||||
|
@ -193,8 +192,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
} else if (this.state.keyMatches === false) {
|
} else if (this.state.keyMatches === false) {
|
||||||
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
|
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
|
||||||
{"\uD83D\uDC4E "}{_t(
|
{"\uD83D\uDC4E "}{_t(
|
||||||
"Unable to access secret storage. Please verify that you " +
|
"Unable to access secret storage. " +
|
||||||
"entered the correct recovery key.",
|
"Please verify that you entered the correct recovery key.",
|
||||||
)}
|
)}
|
||||||
</div>;
|
</div>;
|
||||||
} else if (this.state.recoveryKeyValid) {
|
} else if (this.state.recoveryKeyValid) {
|
||||||
|
@ -209,8 +208,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
|
|
||||||
content = <div>
|
content = <div>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"<b>Warning</b>: You should only access secret storage " +
|
"<b>Warning</b>: You should only do this on a trusted computer.", {},
|
||||||
"from a trusted computer.", {},
|
|
||||||
{ b: sub => <b>{sub}</b> },
|
{ b: sub => <b>{sub}</b> },
|
||||||
)}</p>
|
)}</p>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
|
|
|
@ -34,6 +34,7 @@ import {pillifyLinks, unmountPills} from '../../../utils/pillify';
|
||||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||||
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
|
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
|
||||||
import {toRightOf} from "../../structures/ContextMenu";
|
import {toRightOf} from "../../structures/ContextMenu";
|
||||||
|
import {copyPlaintext} from "../../../utils/strings";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'TextualBody',
|
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
|
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||||
UNSAFE_componentWillMount: function() {
|
UNSAFE_componentWillMount: function() {
|
||||||
this._content = createRef();
|
this._content = createRef();
|
||||||
|
@ -277,17 +261,17 @@ export default createReactClass({
|
||||||
Array.from(ReactDOM.findDOMNode(this).querySelectorAll('.mx_EventTile_body pre')).forEach((p) => {
|
Array.from(ReactDOM.findDOMNode(this).querySelectorAll('.mx_EventTile_body pre')).forEach((p) => {
|
||||||
const button = document.createElement("span");
|
const button = document.createElement("span");
|
||||||
button.className = "mx_EventTile_copyButton";
|
button.className = "mx_EventTile_copyButton";
|
||||||
button.onclick = (e) => {
|
button.onclick = async () => {
|
||||||
const copyCode = button.parentNode.getElementsByTagName("pre")[0];
|
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 GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
|
||||||
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
|
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
|
||||||
...toRightOf(buttonRect, 2),
|
...toRightOf(buttonRect, 2),
|
||||||
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
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
|
// Wrap a div around <pre> so that the copy button can be correctly positioned
|
||||||
|
|
|
@ -18,7 +18,9 @@ import React from 'react';
|
||||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||||
import {_t} from "../../../languageHandler";
|
import {_t} from "../../../languageHandler";
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
@replaceableComponent("views.room_settings.RoomPublishSetting")
|
||||||
export default class RoomPublishSetting extends React.PureComponent {
|
export default class RoomPublishSetting extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { _t, _td } from '../../../languageHandler';
|
||||||
import {PendingActionSpinner} from "../right_panel/EncryptionInfo";
|
import {PendingActionSpinner} from "../right_panel/EncryptionInfo";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
|
import { fixupColorFonts } from '../../../utils/FontManager';
|
||||||
|
|
||||||
function capFirst(s) {
|
function capFirst(s) {
|
||||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
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 = () => {
|
onMatchClick = () => {
|
||||||
this.setState({ pending: true });
|
this.setState({ pending: true });
|
||||||
this.props.onDone();
|
this.props.onDone();
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
"Failure to create room": "Failure to create room",
|
"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 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 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?",
|
"Cancel entering passphrase?": "Cancel entering passphrase?",
|
||||||
"Enter passphrase": "Enter passphrase",
|
"Enter passphrase": "Enter passphrase",
|
||||||
"Cancel": "Cancel",
|
"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)",
|
"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",
|
"Show previews/thumbnails for images": "Show previews/thumbnails for images",
|
||||||
"Enable message search in encrypted rooms": "Enable message search in encrypted rooms",
|
"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.",
|
"How fast should messages be downloaded.": "How fast should messages be downloaded.",
|
||||||
"Manually verify all remote sessions": "Manually verify all remote sessions",
|
"Manually verify all remote sessions": "Manually verify all remote sessions",
|
||||||
"Collecting app version information": "Collecting app version information",
|
"Collecting app version information": "Collecting app version information",
|
||||||
|
@ -1772,18 +1772,19 @@
|
||||||
"Upload %(count)s other files|one": "Upload %(count)s other file",
|
"Upload %(count)s other files|one": "Upload %(count)s other file",
|
||||||
"Cancel All": "Cancel All",
|
"Cancel All": "Cancel All",
|
||||||
"Upload Error": "Upload Error",
|
"Upload Error": "Upload Error",
|
||||||
|
"Verify other session": "Verify other session",
|
||||||
"Verification Request": "Verification Request",
|
"Verification Request": "Verification Request",
|
||||||
"A widget would like to verify your identity": "A widget would like to verify your identity",
|
"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.",
|
"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",
|
"Remember my selection for this widget": "Remember my selection for this widget",
|
||||||
"Allow": "Allow",
|
"Allow": "Allow",
|
||||||
"Deny": "Deny",
|
"Deny": "Deny",
|
||||||
"Enter secret storage passphrase": "Enter secret storage passphrase",
|
"Enter recovery passphrase": "Enter recovery 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.",
|
"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 access secret storage from a trusted computer.": "<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>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 passphrase.": "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 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 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>.",
|
"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 secret storage recovery key": "Enter secret storage recovery key",
|
"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.",
|
"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!",
|
"This looks like a valid recovery key!": "This looks like a valid recovery key!",
|
||||||
"Not a valid recovery key": "Not 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>.",
|
"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",
|
"Unable to load backup status": "Unable to load backup status",
|
||||||
"Recovery key mismatch": "Recovery key mismatch",
|
"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",
|
"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",
|
"Unable to restore backup": "Unable to restore backup",
|
||||||
"No backup found!": "No backup found!",
|
"No backup found!": "No backup found!",
|
||||||
"Backup restored": "Backup restored",
|
"Backup restored": "Backup restored",
|
||||||
"Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!",
|
"Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!",
|
||||||
"Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys",
|
"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.",
|
"<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.",
|
"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>",
|
"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.",
|
"<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.",
|
"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>",
|
"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|zero": "Uploading %(filename)s",
|
||||||
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
|
"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",
|
"Could not load user profile": "Could not load user profile",
|
||||||
"Complete security": "Complete security",
|
|
||||||
"Session verified": "Session verified",
|
"Session verified": "Session verified",
|
||||||
"Failed to send email": "Failed to send email",
|
"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.",
|
"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",
|
"Registration Successful": "Registration Successful",
|
||||||
"Create your account": "Create your account",
|
"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.",
|
"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 can’t access one, <button>use your recovery key or passphrase.</button>": "If you can’t access one, <button>use your recovery key or passphrase.</button>",
|
"If you can’t access one, <button>use your recovery key or recovery passphrase.</button>": "If you can’t access one, <button>use your recovery key or recovery passphrase.</button>",
|
||||||
"Use your other device to continue…": "Use your other device to continue…",
|
"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. 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.",
|
"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",
|
"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.",
|
"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.",
|
"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.",
|
"Great! This recovery passphrase looks strong enough.": "Great! This recovery 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.",
|
"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:",
|
||||||
"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 recovery passphrase": "Enter a recovery passphrase",
|
||||||
"Enter a passphrase": "Enter a passphrase",
|
"Back up encrypted message keys": "Back up encrypted message keys",
|
||||||
"Back up my encryption keys, securing them with the same passphrase": "Back up my encryption keys, securing them with the same passphrase",
|
|
||||||
"Set up with a recovery key": "Set up with a recovery key",
|
"Set up with a recovery key": "Set up with a recovery key",
|
||||||
"That matches!": "That matches!",
|
"That matches!": "That matches!",
|
||||||
"That doesn't match.": "That doesn't match.",
|
"That doesn't match.": "That doesn't match.",
|
||||||
"Go back to set it again.": "Go back to set it again.",
|
"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.",
|
"Enter your recovery passphrase a second time to confirm it.": "Enter your recovery passphrase a second time to confirm it.",
|
||||||
"Confirm your passphrase": "Confirm your passphrase",
|
"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 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 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.",
|
"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",
|
"Your recovery key": "Your recovery key",
|
||||||
"Copy": "Copy",
|
"Copy": "Copy",
|
||||||
|
@ -2205,19 +2202,20 @@
|
||||||
"<b>Copy it</b> to your personal cloud storage": "<b>Copy it</b> to your personal cloud storage",
|
"<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.",
|
"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",
|
"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",
|
"Make a copy of your recovery key": "Make a copy of your recovery key",
|
||||||
"You're done!": "You're done!",
|
"You're done!": "You're done!",
|
||||||
"Unable to set up secret storage": "Unable to set up secret storage",
|
"Unable to set up secret storage": "Unable to set up secret storage",
|
||||||
"Retry": "Retry",
|
"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.",
|
"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...",
|
"Enter a recovery passphrase...": "Enter a recovery passphrase...",
|
||||||
"Please enter your passphrase a second time to confirm.": "Please enter your passphrase a second time to confirm.",
|
"Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.",
|
||||||
"Repeat your passphrase...": "Repeat your passphrase...",
|
"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).",
|
"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.",
|
"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",
|
"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...",
|
"Starting backup...": "Starting backup...",
|
||||||
"Success!": "Success!",
|
"Success!": "Success!",
|
||||||
"Create key backup": "Create key backup",
|
"Create key backup": "Create key backup",
|
||||||
|
|
|
@ -516,7 +516,7 @@ export const SETTINGS = {
|
||||||
},
|
},
|
||||||
"keepSecretStoragePassphraseForSession": {
|
"keepSecretStoragePassphraseForSession": {
|
||||||
supportedLevels: ['device', 'config'],
|
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,
|
default: false,
|
||||||
},
|
},
|
||||||
"crawlerSleepTime": {
|
"crawlerSleepTime": {
|
||||||
|
|
75
src/utils/strings.ts
Normal file
75
src/utils/strings.ts
Normal 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');
|
||||||
|
}
|
|
@ -100,7 +100,7 @@ describe("AccessSecretStorageDialog", function() {
|
||||||
});
|
});
|
||||||
expect(notification.props.children).toEqual(
|
expect(notification.props.children).toEqual(
|
||||||
["\uD83D\uDC4E ", "Unable to access secret storage. Please verify that you " +
|
["\uD83D\uDC4E ", "Unable to access secret storage. Please verify that you " +
|
||||||
"entered the correct passphrase."]);
|
"entered the correct recovery passphrase."]);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue