diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index d15d566bdb..2ecb93e734 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -19,6 +19,7 @@ limitations under the License. } .mx_MessageContextMenu_field { + display: block; padding: 3px 6px 3px 6px; cursor: pointer; white-space: nowrap; diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index 07ec776bd1..c37d0f8bf5 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -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"); diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index 55419cecff..e4e39400f6 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -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 { )}

{_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.", )}

{_t("For maximum security, this should be different from your account password.")}

@@ -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} />
@@ -364,7 +354,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( - "Please enter your passphrase a second time to confirm.", + "Please enter your recovery passphrase a second time to confirm.", )}

@@ -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} />
@@ -393,7 +383,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { return

{_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.", )}

{_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: diff --git a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js index 6588ff5191..9e2264a960 100644 --- a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js +++ b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js @@ -57,8 +57,7 @@ export default class NewRecoveryMethodDialog extends React.PureComponent { ; const newMethodDetected =

{_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.", )}

; const hackWarning =

{_t( diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index d63db617d5..0cc145fd96 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -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

{_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.", - )}

-

{_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:", )}

@@ -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 {
@@ -579,7 +565,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( - "Enter your passphrase a second time to confirm it.", + "Enter your recovery passphrase a second time to confirm it.", )}

@@ -614,7 +600,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { return

{_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.", )}

{_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: diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index 06cece0af2..95128c0be9 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -59,17 +59,17 @@ export default class CompleteSecurity extends React.Component { let title; if (phase === PHASE_INTRO) { - icon = ; - title = _t("Complete security"); + icon = ; + title = _t("Verify this session"); } else if (phase === PHASE_DONE) { - icon = ; + icon = ; title = _t("Session verified"); } else if (phase === PHASE_CONFIRM_SKIP) { - icon = ; + icon = ; title = _t("Are you sure?"); } else if (phase === PHASE_BUSY) { - icon = ; - title = _t("Complete security"); + icon = ; + title = _t("Verify this session"); } else { throw new Error(`Unknown phase ${phase}`); } diff --git a/src/components/structures/auth/SetupEncryptionBody.js b/src/components/structures/auth/SetupEncryptionBody.js index a982957ed0..e6302a4685 100644 --- a/src/components/structures/auth/SetupEncryptionBody.js +++ b/src/components/structures/auth/SetupEncryptionBody.js @@ -116,7 +116,7 @@ export default class SetupEncryptionBody extends React.Component { "granting it access to encrypted messages.", )}

{_t( - "If you can’t access one, ", + "If you can’t access one, ", {}, { button: sub => void; + target: Room | User | Group | RoomMember | MatrixEvent; + permalinkCreator: RoomPermalinkCreator; +} + +interface IState { + linkSpecificEvent: boolean; + permalinkCreator: RoomPermalinkCreator; +} + +export default class ShareDialog extends React.PureComponent { 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 {

; } - - 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 =
@@ -178,14 +192,9 @@ export default class ShareDialog extends React.Component { { _t('Link to selected message') }
; - - 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 { >
diff --git a/src/components/views/dialogs/VerificationRequestDialog.js b/src/components/views/dialogs/VerificationRequestDialog.js index 7ff2cb8f50..3a6e9a2d10 100644 --- a/src/components/views/dialogs/VerificationRequestDialog.js +++ b/src/components/views/dialogs/VerificationRequestDialog.js @@ -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

{_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.", )}

; @@ -291,7 +291,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { title = _t("Incorrect recovery passphrase"); content =

{_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.", )}

; diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index e3a7d7f532..7d7edffcbf 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -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 =
{"\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.", )}
; } else { @@ -135,13 +135,12 @@ export default class AccessSecretStorageDialog extends React.PureComponent { content =

{_t( - "Warning: You should only access secret storage " + - "from a trusted computer.", {}, + "Warning: You should only do this on a trusted computer.", {}, { b: sub => {sub} }, )}

{_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.", )}

@@ -164,7 +163,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { /> {_t( - "If you've forgotten your passphrase you can "+ + "If you've forgotten your recovery passphrase you can "+ "use your recovery key or " + "set up new recovery options." , {}, { @@ -183,7 +182,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { })}
; } 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 =
{"\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.", )}
; } else if (this.state.recoveryKeyValid) { @@ -209,8 +208,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { content =

{_t( - "Warning: You should only access secret storage " + - "from a trusted computer.", {}, + "Warning: You should only do this on a trusted computer.", {}, { b: sub => {sub} }, )}

{_t( diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 27514d0e23..882e331675 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -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

 so that the copy button can be correctly positioned
diff --git a/src/components/views/room_settings/RoomPublishSetting.js b/src/components/views/room_settings/RoomPublishSetting.js
index bac2dfc656..6cc3ce26ba 100644
--- a/src/components/views/room_settings/RoomPublishSetting.js
+++ b/src/components/views/room_settings/RoomPublishSetting.js
@@ -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);
diff --git a/src/components/views/verification/VerificationShowSas.js b/src/components/views/verification/VerificationShowSas.js
index 0a8947f2c2..edf860c4c2 100644
--- a/src/components/views/verification/VerificationShowSas.js
+++ b/src/components/views/verification/VerificationShowSas.js
@@ -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();
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 84c172ea4d..a291065145 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -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.",
-    "Warning: You should only access secret storage from a trusted computer.": "Warning: 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 use your recovery key or set up new recovery options.": "If you've forgotten your passphrase you can use your recovery key or set up new recovery options.",
-    "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.",
+    "Warning: You should only do this on a trusted computer.": "Warning: 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 use your recovery key or set up new recovery options.": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options.",
+    "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 .": "If you've forgotten your recovery key you can .",
     "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",
     "Warning: you should only set up key backup from a trusted computer.": "Warning: 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 use your recovery key or set up new recovery options": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options",
-    "Enter recovery key": "Enter recovery key",
     "Warning: You should only set up key backup from a trusted computer.": "Warning: 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 ": "If you've forgotten your recovery key you can ",
@@ -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 can’t access one, ": "If you can’t access one, ",
+    "If you can’t access one, ": "If you can’t access one, ",
     "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 @@
     "Copy it to your personal cloud storage": "Copy it 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",
diff --git a/src/settings/Settings.js b/src/settings/Settings.js
index b539591efd..232c5d9f66 100644
--- a/src/settings/Settings.js
+++ b/src/settings/Settings.js
@@ -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": {
diff --git a/src/utils/strings.ts b/src/utils/strings.ts
new file mode 100644
index 0000000000..5856682445
--- /dev/null
+++ b/src/utils/strings.ts
@@ -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 {
+    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');
+}
diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.js b/test/components/views/dialogs/AccessSecretStorageDialog-test.js
index 30512ca4dd..c754a4b607 100644
--- a/test/components/views/dialogs/AccessSecretStorageDialog-test.js
+++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.js
@@ -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();
     });
 });