Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
e3f4891489
43 changed files with 701 additions and 356 deletions
|
@ -57,6 +57,7 @@
|
|||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||
@import "./views/dialogs/_EncryptedEventDialog.scss";
|
||||
@import "./views/dialogs/_GroupAddressPicker.scss";
|
||||
@import "./views/dialogs/_IncomingSasDialog.scss";
|
||||
@import "./views/dialogs/_RestoreKeyBackupDialog.scss";
|
||||
@import "./views/dialogs/_RoomSettingsDialog.scss";
|
||||
@import "./views/dialogs/_RoomUpgradeDialog.scss";
|
||||
|
@ -126,6 +127,7 @@
|
|||
@import "./views/rooms/_PinnedEventsPanel.scss";
|
||||
@import "./views/rooms/_PresenceLabel.scss";
|
||||
@import "./views/rooms/_ReplyPreview.scss";
|
||||
@import "./views/rooms/_RoomBreadcrumbs.scss";
|
||||
@import "./views/rooms/_RoomDropTarget.scss";
|
||||
@import "./views/rooms/_RoomHeader.scss";
|
||||
@import "./views/rooms/_RoomList.scss";
|
||||
|
|
|
@ -28,8 +28,8 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_TabbedView_tabLabels {
|
||||
width: 150px;
|
||||
max-width: 150px;
|
||||
width: 170px;
|
||||
max-width: 170px;
|
||||
color: $tab-label-fg-color;
|
||||
position: fixed;
|
||||
}
|
||||
|
@ -39,9 +39,8 @@ limitations under the License.
|
|||
cursor: pointer;
|
||||
display: block;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
min-height: 20px; // use min-height instead of height to allow the label to overflow a bit
|
||||
font-size: 14px;
|
||||
min-height: 24px; // use min-height instead of height to allow the label to overflow a bit
|
||||
margin-bottom: 6px;
|
||||
position: relative;
|
||||
}
|
||||
|
@ -55,8 +54,8 @@ limitations under the License.
|
|||
margin-left: 6px;
|
||||
margin-right: 9px;
|
||||
margin-top: 1px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
@ -64,9 +63,9 @@ limitations under the License.
|
|||
display: inline-block;
|
||||
background-color: $tab-label-icon-bg-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 14px;
|
||||
mask-size: 16px;
|
||||
width: 14px;
|
||||
height: 18px;
|
||||
height: 22px;
|
||||
mask-position: center;
|
||||
content: '';
|
||||
vertical-align: middle;
|
||||
|
@ -81,7 +80,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_TabbedView_tabPanel {
|
||||
margin-left: 220px; // 150px sidebar + 70px padding
|
||||
margin-left: 240px; // 170px sidebar + 70px padding
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
24
res/css/views/dialogs/_IncomingSasDialog.scss
Normal file
24
res/css/views/dialogs/_IncomingSasDialog.scss
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_IncomingSasDialog_opponentProfile_image {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_IncomingSasDialog_opponentProfile h2 {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
|
@ -16,8 +16,8 @@ limitations under the License.
|
|||
|
||||
.mx_SettingsDialog {
|
||||
.mx_Dialog {
|
||||
max-width: 900px;
|
||||
width: 80%;
|
||||
max-width: 1000px;
|
||||
width: 90%;
|
||||
height: 80%;
|
||||
border-radius: 4px;
|
||||
padding-top: 0;
|
||||
|
@ -30,7 +30,7 @@ limitations under the License.
|
|||
|
||||
.mx_TabbedView .mx_SettingsTab {
|
||||
box-sizing: border-box;
|
||||
min-width: 550px;
|
||||
min-width: 580px;
|
||||
padding-right: 130px;
|
||||
|
||||
// Put some padding on the bottom to avoid the settings tab from
|
||||
|
|
|
@ -28,28 +28,28 @@ limitations under the License.
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mx_UnknownDeviceDialog ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
// userid
|
||||
.mx_UnknownDeviceDialog p {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mx_UnknownDeviceDialog .mx_DeviceVerifyButtons {
|
||||
float: right;
|
||||
flex-direction: row !important;
|
||||
}
|
||||
|
||||
.mx_UnknownDeviceDialog .mx_Dialog_content {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.mx_UnknownDeviceDialog .mx_MemberDeviceInfo {
|
||||
float: right;
|
||||
clear: both;
|
||||
padding: 0px;
|
||||
padding-top: 8px;
|
||||
.mx_UnknownDeviceDialog_deviceList > li {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.mx_UnknownDeviceDialog .mx_MemberDeviceInfo_textButton {
|
||||
@mixin mx_DialogButton_small;
|
||||
background-color: $primary-bg-color;
|
||||
color: $accent-color;
|
||||
}
|
||||
|
||||
.mx_UnknownDeviceDialog .mx_UnknownDeviceDialog_deviceList li {
|
||||
height: 40px;
|
||||
border-bottom: 1px solid $primary-hairline-color;
|
||||
.mx_UnknownDeviceDialog_deviceList > li > * {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,16 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
the tile title bar is 5 (top border) + 12 (title, buttons) + 5 (bottom padding) px = 22px
|
||||
the body is assumed to be 300px (assumed by at least the sticker pickerm, perhaps elsewhere),
|
||||
so the body height would be 300px - 22px (room for title bar) = 278px
|
||||
BUT! the sticker picker also assumes it's a little less high than that because the iframe
|
||||
for the sticker picker doesn't have any padding or margin on it's bottom.
|
||||
so subtracking another 5px, which brings us at 273px.
|
||||
*/
|
||||
$AppsDrawerBodyHeight: 273px;
|
||||
|
||||
.mx_AppsDrawer {
|
||||
margin: 5px;
|
||||
}
|
||||
|
@ -83,7 +93,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_AppTile_persistedWrapper {
|
||||
height: 280px;
|
||||
height: $AppsDrawerBodyHeight;
|
||||
}
|
||||
|
||||
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
|
||||
|
@ -189,7 +199,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_AppTileBody{
|
||||
height: 280px;
|
||||
height: $AppsDrawerBodyHeight;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -208,7 +218,7 @@ limitations under the License.
|
|||
|
||||
.mx_AppTileBody iframe {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
height: $AppsDrawerBodyHeight;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
@ -332,7 +342,7 @@ form.mx_Custom_Widget_Form div {
|
|||
align-items: center;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
height: 280px;
|
||||
height: $AppsDrawerBodyHeight;
|
||||
}
|
||||
|
||||
.mx_AppLoading .mx_Spinner {
|
||||
|
|
|
@ -533,7 +533,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_EventTile_e2eIcon {
|
||||
top: 7px;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.mx_EventTile_editButton {
|
||||
|
|
54
res/css/views/rooms/_RoomBreadcrumbs.scss
Normal file
54
res/css/views/rooms/_RoomBreadcrumbs.scss
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_RoomBreadcrumbs {
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
height: 32px;
|
||||
margin: 8px;
|
||||
margin-bottom: 0;
|
||||
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
> * {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 15px;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, rgba(242,245,248,0), rgba(242,245,248,1));
|
||||
}
|
||||
|
||||
.mx_RoomBreadcrumbs_animate {
|
||||
margin-left: 0;
|
||||
transition: transform 0.3s, width 0.3s;
|
||||
width: 32px;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.mx_RoomBreadcrumbs_preAnimate {
|
||||
width: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -154,7 +154,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_RoomTile_unread, .mx_RoomTile_highlight {
|
||||
font-weight: 700 ! important;
|
||||
font-weight: 700 !important;
|
||||
|
||||
.mx_RoomTile_name {
|
||||
color: $roomtile-selected-color;
|
||||
|
@ -176,7 +176,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_RoomTile:focus {
|
||||
filter: none ! important;
|
||||
filter: none !important;
|
||||
background-color: $roomtile-focused-bg-color;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,12 @@
|
|||
height: 300px;
|
||||
}
|
||||
|
||||
.mx_Stickers_content .mx_AppTileFullWidth {
|
||||
border: none;
|
||||
#mx_persistedElement_stickerPicker .mx_AppTileFullWidth {
|
||||
height: unset;
|
||||
box-sizing: border-box;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.mx_Stickers_contentPlaceholder {
|
||||
|
|
|
@ -21,7 +21,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_SettingsTab_subheading {
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
font-family: $font-family;
|
||||
font-weight: 600;
|
||||
|
@ -32,7 +32,7 @@ limitations under the License.
|
|||
|
||||
.mx_SettingsTab_subsectionText {
|
||||
color: $settings-subsection-fg-color;
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
padding-bottom: 12px;
|
||||
display: block;
|
||||
margin: 0 100px 0 0; // Align with the rest of the view
|
||||
|
@ -40,16 +40,17 @@ limitations under the License.
|
|||
|
||||
.mx_SettingsTab_section .mx_SettingsFlag {
|
||||
margin-right: 100px;
|
||||
height: 25px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mx_SettingsTab_section .mx_SettingsFlag .mx_SettingsFlag_label {
|
||||
vertical-align: bottom;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
color: $primary-fg-color;
|
||||
max-width: calc(100% - 48px); // Force word wrap instead of colliding with the switch
|
||||
box-sizing: border-box;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.mx_SettingsTab_section .mx_SettingsFlag .mx_ToggleSwitch {
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="12" viewBox="0 0 14 12">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#454545" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M6.018 1.532l-4.864 7.81c-.204.34-.205.76-.003 1.1.202.341.577.554.985.558h9.728c.408-.004.783-.217.985-.558.202-.34.201-.76-.003-1.1l-4.864-7.81A1.159 1.159 0 0 0 7 1c-.401 0-.774.202-.982.532zM7 3v4"/>
|
||||
</g>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 14 12" style="enable-background:new 0 0 14 12;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#454545;stroke-linecap:round;stroke-linejoin:round;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M6,1.5L1.2,9.3c-0.2,0.3-0.2,0.8,0,1.1c0.2,0.3,0.6,0.6,1,0.6h9.7c0.4,0,0.8-0.2,1-0.6c0.2-0.3,0.2-0.8,0-1.1
|
||||
L8,1.5C7.8,1.2,7.4,1,7,1C6.6,1,6.2,1.2,6,1.5z M7,4v3"/>
|
||||
</g>
|
||||
<line class="st0" x1="7" y1="9" x2="7" y2="9"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 425 B After Width: | Height: | Size: 704 B |
|
@ -32,7 +32,7 @@ module.exports = {
|
|||
return false;
|
||||
} else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') {
|
||||
return false;
|
||||
} else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
|
||||
} else if (ev.getType() == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
|
||||
return false;
|
||||
}
|
||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||
|
|
|
@ -240,7 +240,6 @@ export default React.createClass({
|
|||
|
||||
_renderPhasePassPhrase: function() {
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
let strengthMeter;
|
||||
let helpText;
|
||||
|
@ -265,8 +264,15 @@ export default React.createClass({
|
|||
}
|
||||
|
||||
return <div>
|
||||
<p>{_t("Secure your encrypted message history with a Recovery Passphrase.")}</p>
|
||||
<p>{_t("You'll need it if you log out or lose access to this device.")}</p>
|
||||
<p>{_t(
|
||||
"<b>Warning</b>: you should only set up key backup from a trusted computer.", {},
|
||||
{ b: sub => <b>{sub}</b> },
|
||||
)}</p>
|
||||
<p>{_t(
|
||||
"We'll store an encrypted copy of your keys on our server. " +
|
||||
"Protect your backup with a passphrase to keep it secure.",
|
||||
)}</p>
|
||||
<p>{_t("For maximum security, this should be different from your account password.")}</p>
|
||||
|
||||
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
||||
|
@ -291,34 +297,12 @@ export default React.createClass({
|
|||
disabled={!this._passPhraseIsValid()}
|
||||
/>
|
||||
|
||||
<p>{_t(
|
||||
"If you don't want encrypted message history to be available on other devices, "+
|
||||
"<button>opt out</button>.",
|
||||
{},
|
||||
{
|
||||
button: sub => <AccessibleButton
|
||||
element="span"
|
||||
className="mx_linkButton"
|
||||
onClick={this._onOptOutClick}
|
||||
>
|
||||
{sub}
|
||||
</AccessibleButton>,
|
||||
},
|
||||
)}</p>
|
||||
<p>{_t(
|
||||
"Or, if you don't want to create a Recovery Passphrase, skip this step and "+
|
||||
"<button>download a recovery key</button>.",
|
||||
{},
|
||||
{
|
||||
button: sub => <AccessibleButton
|
||||
element="span"
|
||||
className="mx_linkButton"
|
||||
onClick={this._onSkipPassPhraseClick}
|
||||
>
|
||||
{sub}
|
||||
</AccessibleButton>,
|
||||
},
|
||||
)}</p>
|
||||
<details>
|
||||
<summary>{_t("Advanced")}</summary>
|
||||
<p><button onClick={this._onSkipPassPhraseClick} >
|
||||
{_t("Set up with a Recovery Key")}
|
||||
</button></p>
|
||||
</details>
|
||||
</div>;
|
||||
},
|
||||
|
||||
|
@ -353,9 +337,7 @@ export default React.createClass({
|
|||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
return <div>
|
||||
<p>{_t(
|
||||
"Type in your Recovery Passphrase to confirm you remember it. " +
|
||||
"If it helps, add it to your password manager or store it " +
|
||||
"somewhere safe.",
|
||||
"Please enter your passphrase a second time to confirm.",
|
||||
)}</p>
|
||||
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
||||
|
@ -392,7 +374,13 @@ export default React.createClass({
|
|||
}
|
||||
|
||||
return <div>
|
||||
<p>{_t("Make a copy of this Recovery Key and keep it safe.")}</p>
|
||||
<p>{_t(
|
||||
"Your recovery key is a safety net - you can use it to restore " +
|
||||
"access to your encrypted messages if you forget your passphrase.",
|
||||
)}</p>
|
||||
<p>{_t(
|
||||
"Keep your recovery key somewhere very secure, like a password manager (or a safe)",
|
||||
)}</p>
|
||||
<p>{bodyText}</p>
|
||||
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||
<div className="mx_CreateKeyBackupDialog_recoveryKeyHeader">
|
||||
|
@ -455,10 +443,9 @@ export default React.createClass({
|
|||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
return <div>
|
||||
<p>{_t(
|
||||
"Your encryption keys are now being backed up in the background " +
|
||||
"to your Homeserver. The initial backup could take several minutes. " +
|
||||
"You can view key backup upload progress in Settings.")}</p>
|
||||
<DialogButtons primaryButton={_t('Close')}
|
||||
"Your keys are being backed up (the first backup could take a few minutes).",
|
||||
)}</p>
|
||||
<DialogButtons primaryButton={_t('Okay')}
|
||||
onPrimaryButtonClick={this._onDone}
|
||||
hasCancel={false}
|
||||
/>
|
||||
|
@ -484,19 +471,19 @@ export default React.createClass({
|
|||
_titleForPhase: function(phase) {
|
||||
switch (phase) {
|
||||
case PHASE_PASSPHRASE:
|
||||
return _t('Create a Recovery Passphrase');
|
||||
return _t('Secure your backup with a passphrase');
|
||||
case PHASE_PASSPHRASE_CONFIRM:
|
||||
return _t('Confirm Recovery Passphrase');
|
||||
return _t('Confirm your passphrase');
|
||||
case PHASE_OPTOUT_CONFIRM:
|
||||
return _t('Warning!');
|
||||
case PHASE_SHOWKEY:
|
||||
return _t('Recovery Key');
|
||||
return _t('Recovery key');
|
||||
case PHASE_KEEPITSAFE:
|
||||
return _t('Keep it safe');
|
||||
case PHASE_BACKINGUP:
|
||||
return _t('Starting backup...');
|
||||
case PHASE_DONE:
|
||||
return _t('Backup Started');
|
||||
return _t('Success!');
|
||||
default:
|
||||
return _t("Create Key Backup");
|
||||
}
|
||||
|
|
|
@ -39,36 +39,8 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
|
|||
}
|
||||
|
||||
onSetupClick = async () => {
|
||||
// TODO: Should change to a restore key backup flow that checks the
|
||||
// recovery passphrase while at the same time also cross-signing the
|
||||
// device as well in a single flow. Since we don't have that yet, we'll
|
||||
// look for an unverified device and verify it. Note that this means
|
||||
// we won't restore keys yet; instead we'll only trust the backup for
|
||||
// sending our own new keys to it.
|
||||
let backupSigStatus;
|
||||
try {
|
||||
backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(this.props.newVersionInfo);
|
||||
} catch (e) {
|
||||
console.log("Unable to fetch key backup status", e);
|
||||
return;
|
||||
}
|
||||
|
||||
let unverifiedDevice;
|
||||
for (const sig of backupSigStatus.sigs) {
|
||||
if (!sig.device.isVerified()) {
|
||||
unverifiedDevice = sig.device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!unverifiedDevice) {
|
||||
console.log("Unable to find a device to verify.");
|
||||
return;
|
||||
}
|
||||
|
||||
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
|
||||
Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, {
|
||||
userId: MatrixClientPeg.get().credentials.userId,
|
||||
device: unverifiedDevice,
|
||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
||||
Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
||||
onFinished: this.props.onFinished,
|
||||
});
|
||||
}
|
||||
|
@ -111,11 +83,6 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
|
|||
} else {
|
||||
content = <div>
|
||||
{newMethodDetected}
|
||||
<p>{_t(
|
||||
"Setting up Secure Messages on this device " +
|
||||
"will re-encrypt this device's message history with " +
|
||||
"the new recovery method.",
|
||||
)}</p>
|
||||
{hackWarning}
|
||||
<DialogButtons
|
||||
primaryButton={_t("Set up Secure Messages")}
|
||||
|
|
|
@ -182,6 +182,7 @@ const LeftPanel = React.createClass({
|
|||
|
||||
render: function() {
|
||||
const RoomList = sdk.getComponent('rooms.RoomList');
|
||||
const RoomBreadcrumbs = sdk.getComponent('rooms.RoomBreadcrumbs');
|
||||
const TagPanel = sdk.getComponent('structures.TagPanel');
|
||||
const CustomRoomTagPanel = sdk.getComponent('structures.CustomRoomTagPanel');
|
||||
const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton');
|
||||
|
@ -215,12 +216,17 @@ const LeftPanel = React.createClass({
|
|||
onCleared={ this.onSearchCleared }
|
||||
collapsed={this.props.collapsed} />);
|
||||
|
||||
let breadcrumbs;
|
||||
if (SettingsStore.isFeatureEnabled("feature_room_breadcrumbs")) {
|
||||
breadcrumbs = (<RoomBreadcrumbs collapsed={this.props.collapsed} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={containerClasses}>
|
||||
{ tagPanelContainer }
|
||||
<aside className={"mx_LeftPanel dark-panel"} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
|
||||
<TopLeftMenuButton collapsed={ this.props.collapsed } />
|
||||
{ breadcrumbs }
|
||||
{ searchBox }
|
||||
<CallPreview ConferenceHandler={VectorConferenceHandler} />
|
||||
<RoomList
|
||||
|
|
|
@ -574,11 +574,8 @@ export default React.createClass({
|
|||
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
||||
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {}, 'mx_SettingsDialog');
|
||||
|
||||
// View the home page if we need something to look at
|
||||
if (!this.state.currentGroupId && !this.state.currentRoomId) {
|
||||
this._setPage(PageTypes.HomePage);
|
||||
this.notifyNewScreen('home');
|
||||
}
|
||||
// View the welcome or home page if we need something to look at
|
||||
this._viewSomethingBehindModal();
|
||||
break;
|
||||
}
|
||||
case 'view_create_room':
|
||||
|
@ -595,11 +592,8 @@ export default React.createClass({
|
|||
config: this.props.config,
|
||||
}, 'mx_RoomDirectory_dialogWrapper');
|
||||
|
||||
// View the home page if we need something to look at
|
||||
if (!this.state.currentGroupId && !this.state.currentRoomId) {
|
||||
this._setPage(PageTypes.HomePage);
|
||||
this.notifyNewScreen('home');
|
||||
}
|
||||
// View the welcome or home page if we need something to look at
|
||||
this._viewSomethingBehindModal();
|
||||
}
|
||||
break;
|
||||
case 'view_my_groups':
|
||||
|
@ -825,6 +819,7 @@ export default React.createClass({
|
|||
this.focusComposer = true;
|
||||
|
||||
const newState = {
|
||||
view: VIEWS.LOGGED_IN,
|
||||
currentRoomId: roomInfo.room_id || null,
|
||||
page_type: PageTypes.RoomView,
|
||||
thirdPartyInvite: roomInfo.third_party_invite,
|
||||
|
@ -887,6 +882,16 @@ export default React.createClass({
|
|||
this.notifyNewScreen('group/' + groupId);
|
||||
},
|
||||
|
||||
_viewSomethingBehindModal() {
|
||||
if (this.state.view !== VIEWS.LOGGED_IN) {
|
||||
this._viewWelcome();
|
||||
return;
|
||||
}
|
||||
if (!this.state.currentGroupId && !this.state.currentRoomId) {
|
||||
this._viewHome();
|
||||
}
|
||||
},
|
||||
|
||||
_viewWelcome() {
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.WELCOME,
|
||||
|
@ -1552,11 +1557,7 @@ export default React.createClass({
|
|||
payload.room_id = roomString;
|
||||
}
|
||||
|
||||
// we can't view a room unless we're logged in
|
||||
// (a guest account is fine)
|
||||
if (this.state.view === VIEWS.LOGGED_IN) {
|
||||
dis.dispatch(payload);
|
||||
}
|
||||
dis.dispatch(payload);
|
||||
} else if (screen.indexOf('user/') == 0) {
|
||||
const userId = screen.substring(5);
|
||||
|
||||
|
|
|
@ -78,6 +78,11 @@ module.exports = React.createClass({
|
|||
this.protocols = null;
|
||||
|
||||
this.setState({protocolsLoading: true});
|
||||
if (!MatrixClientPeg.get()) {
|
||||
// We may not have a client yet when invoked from welcome page
|
||||
this.setState({protocolsLoading: false});
|
||||
return;
|
||||
}
|
||||
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
|
||||
this.protocols = response;
|
||||
this.setState({protocolsLoading: false});
|
||||
|
|
|
@ -145,6 +145,7 @@ const RoomSubList = React.createClass({
|
|||
collapsed={this.props.collapsed || false}
|
||||
unread={Unread.doesRoomHaveUnreadMessages(room)}
|
||||
highlight={room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite}
|
||||
notificationCount={room.getUnreadNotificationCount()}
|
||||
isInvite={this.props.isInvite}
|
||||
refreshSubList={this._updateSubListCount}
|
||||
incomingCall={null}
|
||||
|
|
|
@ -283,6 +283,15 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_getRoomId() {
|
||||
// According to `_onRoomViewStoreUpdate`, `state.roomId` can be null
|
||||
// if we have a room alias we haven't resolved yet. To work around this,
|
||||
// first we'll try the room object if it's there, and then fallback to
|
||||
// the bare room ID. (We may want to update `state.roomId` after
|
||||
// resolving aliases, so we could always trust it.)
|
||||
return this.state.room ? this.state.room.roomId : this.state.roomId;
|
||||
},
|
||||
|
||||
_onWidgetEchoStoreUpdate: function() {
|
||||
this.setState({
|
||||
showApps: this._shouldShowApps(this.state.room),
|
||||
|
@ -784,6 +793,7 @@ module.exports = React.createClass({
|
|||
this._updateConfCallNotification();
|
||||
this._updateDMState();
|
||||
this._checkIfAlone(this.state.room);
|
||||
this._updateE2EStatus(this.state.room);
|
||||
}, 500),
|
||||
|
||||
_checkIfAlone: function(room) {
|
||||
|
@ -877,13 +887,12 @@ module.exports = React.createClass({
|
|||
// If the user is a ROU, allow them to transition to a PWLU
|
||||
if (cli && cli.isGuest()) {
|
||||
// Join this room once the user has registered and logged in
|
||||
const signUrl = this.props.thirdPartyInvite ?
|
||||
this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
||||
// (If we failed to peek, we may not have a valid room object.)
|
||||
dis.dispatch({
|
||||
action: 'do_after_sync_prepared',
|
||||
deferred_action: {
|
||||
action: 'view_room',
|
||||
room_id: this.state.room.roomId,
|
||||
room_id: this._getRoomId(),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import { _t } from '../../languageHandler';
|
|||
import { KeyCode } from '../../Keyboard';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import rate_limited_func from '../../ratelimitedfunc';
|
||||
import { debounce } from 'lodash';
|
||||
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
@ -67,12 +67,9 @@ module.exports = React.createClass({
|
|||
this.onSearch();
|
||||
},
|
||||
|
||||
onSearch: new rate_limited_func(
|
||||
function() {
|
||||
this.props.onSearch(this.refs.search.value);
|
||||
},
|
||||
500,
|
||||
),
|
||||
onSearch: debounce(function() {
|
||||
this.props.onSearch(this.refs.search.value);
|
||||
}, 200, {trailing: true}),
|
||||
|
||||
_onKeyDown: function(ev) {
|
||||
switch (ev.keyCode) {
|
||||
|
|
|
@ -90,6 +90,11 @@ module.exports = React.createClass({
|
|||
this.closeMenu();
|
||||
},
|
||||
|
||||
e2eInfoClicked: function() {
|
||||
this.props.e2eInfoCallback();
|
||||
this.closeMenu();
|
||||
},
|
||||
|
||||
onViewSourceClick: function() {
|
||||
const ViewSource = sdk.getComponent('structures.ViewSource');
|
||||
Modal.createTrackedDialog('View Event Source', '', ViewSource, {
|
||||
|
@ -332,6 +337,13 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
|
||||
let e2eInfo;
|
||||
if (this.props.e2eInfoCallback) {
|
||||
e2eInfo = <div className="mx_MessageContextMenu_field" onClick={this.e2eInfoClicked}>
|
||||
{ _t('End-to-end encryption information') }
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_MessageContextMenu">
|
||||
{ resendButton }
|
||||
|
@ -347,6 +359,7 @@ module.exports = React.createClass({
|
|||
{ replyButton }
|
||||
{ externalURLButton }
|
||||
{ collapseReplyThread }
|
||||
{ e2eInfo }
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -68,7 +68,7 @@ export default class TagTileContextMenu extends React.Component {
|
|||
<hr className="mx_TagTileContextMenu_separator" />
|
||||
<div className="mx_TagTileContextMenu_item" onClick={this._onRemoveClick} >
|
||||
<img className="mx_TagTileContextMenu_item_icon" src={require("../../../../res/img/icon_context_delete.svg")} width="15" height="15" />
|
||||
{ _t('Remove') }
|
||||
{ _t('Hide') }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,11 @@ export default class DeviceVerifyDialog extends React.Component {
|
|||
}
|
||||
|
||||
_onSwitchToLegacyClick = () => {
|
||||
if (this._verifier) {
|
||||
this._verifier.removeListener('show_sas', this._onVerifierShowSas);
|
||||
this._verifier.cancel('User cancel');
|
||||
this._verifier = null;
|
||||
}
|
||||
this.setState({mode: MODE_LEGACY});
|
||||
}
|
||||
|
||||
|
@ -184,11 +189,21 @@ export default class DeviceVerifyDialog extends React.Component {
|
|||
|
||||
_renderSasVerificationPhaseWaitAccept() {
|
||||
const Spinner = sdk.getComponent("views.elements.Spinner");
|
||||
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Spinner />
|
||||
<p>{_t("Waiting for partner to accept...")}</p>
|
||||
<p>{_t(
|
||||
"Nothing appearing? Not all clients support interactive verification yet. " +
|
||||
"<button>Use legacy verification</button>.",
|
||||
{}, {button: sub => <AccessibleButton element='span' className="mx_linkButton"
|
||||
onClick={this._onSwitchToLegacyClick}
|
||||
>
|
||||
{sub}
|
||||
</AccessibleButton>},
|
||||
)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
|
@ -37,9 +38,12 @@ export default class IncomingSasDialog extends React.Component {
|
|||
this.state = {
|
||||
phase: PHASE_START,
|
||||
sasVerified: false,
|
||||
opponentProfile: null,
|
||||
opponentProfileError: null,
|
||||
};
|
||||
this.props.verifier.on('show_sas', this._onVerifierShowSas);
|
||||
this.props.verifier.on('cancel', this._onVerifierCancel);
|
||||
this._fetchOpponentProfile();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -49,6 +53,21 @@ export default class IncomingSasDialog extends React.Component {
|
|||
this.props.verifier.removeListener('show_sas', this._onVerifierShowSas);
|
||||
}
|
||||
|
||||
async _fetchOpponentProfile() {
|
||||
try {
|
||||
const prof = await MatrixClientPeg.get().getProfileInfo(
|
||||
this.props.verifier.userId,
|
||||
);
|
||||
this.setState({
|
||||
opponentProfile: prof,
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState({
|
||||
opponentProfileError: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_onFinished = () => {
|
||||
this.props.onFinished(this.state.phase === PHASE_VERIFIED);
|
||||
}
|
||||
|
@ -93,10 +112,39 @@ export default class IncomingSasDialog extends React.Component {
|
|||
|
||||
_renderPhaseStart() {
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
const Spinner = sdk.getComponent("views.elements.Spinner");
|
||||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||
|
||||
let profile;
|
||||
if (this.state.opponentProfile) {
|
||||
profile = <div className="mx_IncomingSasDialog_opponentProfile">
|
||||
<BaseAvatar name={this.state.opponentProfile.displayname}
|
||||
idName={this.props.verifier.userId}
|
||||
url={MatrixClientPeg.get().mxcUrlToHttp(
|
||||
this.state.opponentProfile.avatar_url,
|
||||
Math.floor(48 * window.devicePixelRatio),
|
||||
Math.floor(48 * window.devicePixelRatio),
|
||||
'crop',
|
||||
)}
|
||||
width={48} height={48} resizeMethod='crop'
|
||||
/>
|
||||
<h2>{this.state.opponentProfile.displayname}</h2>
|
||||
</div>;
|
||||
} else if (this.state.opponentProfileError) {
|
||||
profile = <div>
|
||||
<BaseAvatar name={this.props.verifier.userId.slice(1)}
|
||||
idName={this.props.verifier.userId}
|
||||
width={48} height={48}
|
||||
/>
|
||||
<h2>{this.props.verifier.userId}</h2>
|
||||
</div>;
|
||||
} else {
|
||||
profile = <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{this.props.verifier.userId}</h2>
|
||||
{profile}
|
||||
<p>{_t(
|
||||
"Verify this user to mark them as trusted. " +
|
||||
"Trusting users gives you extra peace of mind when using " +
|
||||
|
|
|
@ -22,6 +22,10 @@ import { _t } from '../../../languageHandler';
|
|||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
|
||||
export default class LogoutDialog extends React.Component {
|
||||
defaultProps = {
|
||||
onFinished: function() {},
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._onSettingsLinkClick = this._onSettingsLinkClick.bind(this);
|
||||
|
@ -29,13 +33,37 @@ export default class LogoutDialog extends React.Component {
|
|||
this._onFinished = this._onFinished.bind(this);
|
||||
this._onSetRecoveryMethodClick = this._onSetRecoveryMethodClick.bind(this);
|
||||
this._onLogoutConfirm = this._onLogoutConfirm.bind(this);
|
||||
|
||||
this.state = {
|
||||
loading: false,
|
||||
backupInfo: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
|
||||
this._loadBackupStatus();
|
||||
}
|
||||
}
|
||||
|
||||
async _loadBackupStatus() {
|
||||
try {
|
||||
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||
this.setState({
|
||||
loading: false,
|
||||
backupInfo,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Unable to fetch key backup status", e);
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_onSettingsLinkClick() {
|
||||
// close dialog
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
this.props.onFinished();
|
||||
}
|
||||
|
||||
_onExportE2eKeysClicked() {
|
||||
|
@ -52,9 +80,7 @@ export default class LogoutDialog extends React.Component {
|
|||
dis.dispatch({action: 'logout'});
|
||||
}
|
||||
// close dialog
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
this.props.onFinished();
|
||||
}
|
||||
|
||||
_onSetRecoveryMethodClick() {
|
||||
|
@ -63,72 +89,83 @@ export default class LogoutDialog extends React.Component {
|
|||
);
|
||||
|
||||
// close dialog
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
this.props.onFinished();
|
||||
}
|
||||
|
||||
_onLogoutConfirm() {
|
||||
dis.dispatch({action: 'logout'});
|
||||
|
||||
// close dialog
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
this.props.onFinished();
|
||||
}
|
||||
|
||||
render() {
|
||||
const description = <div>
|
||||
<p>{_t(
|
||||
"When you log out, you'll lose your secure message history. To prevent " +
|
||||
"this, set up a recovery method.",
|
||||
)}</p>
|
||||
<p>{_t(
|
||||
"Alternatively, advanced users can also manually export encryption keys in " +
|
||||
"<a>Settings</a> before logging out.", {},
|
||||
{
|
||||
a: sub => <a href='#/settings' onClick={this._onSettingsLinkClick}>{sub}</a>,
|
||||
},
|
||||
"Encrypted messages are secured with end-to-end encryption. " +
|
||||
"Only you and the recipient(s) have the keys to read these messages.",
|
||||
)}</p>
|
||||
<p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
|
||||
</div>;
|
||||
|
||||
if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
let dialogContent;
|
||||
if (this.state.loading) {
|
||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||
|
||||
dialogContent = <Spinner />;
|
||||
} else {
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
let setupButtonCaption;
|
||||
if (this.state.backupInfo) {
|
||||
setupButtonCaption = _t("Use Key Backup");
|
||||
} else {
|
||||
// if there's an error fetching the backup info, we'll just assume there's
|
||||
// no backup for the purpose of the button caption
|
||||
setupButtonCaption = _t("Start using Key Backup");
|
||||
}
|
||||
|
||||
dialogContent = <div>
|
||||
<div className="mx_Dialog_content" id='mx_Dialog_content'>
|
||||
{ description }
|
||||
</div>
|
||||
<DialogButtons primaryButton={setupButtonCaption}
|
||||
hasCancel={false}
|
||||
onPrimaryButtonClick={this._onSetRecoveryMethodClick}
|
||||
focus={true}
|
||||
>
|
||||
<button onClick={this._onLogoutConfirm}>
|
||||
{_t("I don't want my encrypted messages")}
|
||||
</button>
|
||||
</DialogButtons>
|
||||
<details>
|
||||
<summary>{_t("Advanced")}</summary>
|
||||
<p><button onClick={this._onExportE2eKeysClicked}>
|
||||
{_t("Manually export keys")}
|
||||
</button></p>
|
||||
</details>
|
||||
</div>;
|
||||
}
|
||||
// Not quite a standard question dialog as the primary button cancels
|
||||
// the action and does something else instead, whilst non-default button
|
||||
// confirms the action.
|
||||
return (<BaseDialog
|
||||
title={_t("Warning!")}
|
||||
title={_t("You'll lose access to your encrypted messages")}
|
||||
contentId='mx_Dialog_content'
|
||||
hasCancel={false}
|
||||
onFinsihed={this._onFinished}
|
||||
hasCancel={true}
|
||||
onFinished={this._onFinished}
|
||||
>
|
||||
<div className="mx_Dialog_content" id='mx_Dialog_content'>
|
||||
{ description }
|
||||
</div>
|
||||
<DialogButtons primaryButton={_t('Set a Recovery Method')}
|
||||
hasCancel={false}
|
||||
onPrimaryButtonClick={this._onSetRecoveryMethodClick}
|
||||
focus={true}
|
||||
>
|
||||
<button onClick={this._onLogoutConfirm}>
|
||||
{_t("I understand, log out without")}
|
||||
</button>
|
||||
</DialogButtons>
|
||||
{dialogContent}
|
||||
</BaseDialog>);
|
||||
} else {
|
||||
const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
|
||||
return (<QuestionDialog
|
||||
hasCancelButton={true}
|
||||
title={_t("Sign out")}
|
||||
// TODO: This is made up by me and would need to also mention verifying
|
||||
// once you can restore a backup by verifying a device
|
||||
description={_t(
|
||||
"When signing in again, you can access encrypted chat history by " +
|
||||
"restoring your key backup. You'll need your recovery passphrase " +
|
||||
"or, if you didn't set a recovery passphrase, your recovery key " +
|
||||
"(that you downloaded).",
|
||||
"Are you sure you want to sign out?",
|
||||
)}
|
||||
button={_t("Sign out")}
|
||||
onFinished={this._onFinished}
|
||||
|
|
|
@ -25,35 +25,12 @@ import { _t } from '../../../languageHandler';
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { markAllDevicesKnown } from '../../../cryptodevices';
|
||||
|
||||
function DeviceListEntry(props) {
|
||||
const {userId, device} = props;
|
||||
|
||||
const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
|
||||
|
||||
return (
|
||||
<li>
|
||||
{ device.deviceId }
|
||||
<DeviceVerifyButtons device={device} userId={userId} />
|
||||
<br />
|
||||
{ device.getDisplayName() }
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
DeviceListEntry.propTypes = {
|
||||
userId: PropTypes.string.isRequired,
|
||||
|
||||
// deviceinfo
|
||||
device: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
||||
function UserUnknownDeviceList(props) {
|
||||
const MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo');
|
||||
const {userId, userDevices} = props;
|
||||
|
||||
const deviceListEntries = Object.keys(userDevices).map((deviceId) =>
|
||||
<DeviceListEntry key={deviceId} userId={userId}
|
||||
device={userDevices[deviceId]} />,
|
||||
<li key={deviceId}><MemberDeviceInfo device={userDevices[deviceId]} userId={userId} showDeviceId={true} /></li>,
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -230,10 +230,15 @@ export default React.createClass({
|
|||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
title = _t("Enter Recovery Passphrase");
|
||||
content = <div>
|
||||
{_t(
|
||||
<p>{_t(
|
||||
"<b>Warning</b>: you should only set up key backup " +
|
||||
"from a trusted computer.", {},
|
||||
{ b: sub => <b>{sub}</b> },
|
||||
)}</p>
|
||||
<p>{_t(
|
||||
"Access your secure message history and set up secure " +
|
||||
"messaging by entering your recovery passphrase.",
|
||||
)}<br />
|
||||
)}</p>
|
||||
|
||||
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
|
||||
<input type="password"
|
||||
|
@ -288,10 +293,15 @@ export default React.createClass({
|
|||
}
|
||||
|
||||
content = <div>
|
||||
{_t(
|
||||
<p>{_t(
|
||||
"<b>Warning</b>: you should only set up key backup " +
|
||||
"from a trusted computer.", {},
|
||||
{ b: sub => <b>{sub}</b> },
|
||||
)}</p>
|
||||
<p>{_t(
|
||||
"Access your secure message history and set up secure " +
|
||||
"messaging by entering your recovery key.",
|
||||
)}<br />
|
||||
)}</p>
|
||||
|
||||
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
|
||||
<input className="mx_RestoreKeyBackupDialog_recoveryKeyInput"
|
||||
|
|
|
@ -579,8 +579,8 @@ export default class AppTile extends React.Component {
|
|||
// editing is done in scalar
|
||||
const canUserModify = this._canUserModify();
|
||||
const showEditButton = Boolean(this._scalarClient && canUserModify);
|
||||
const showDeleteButton = canUserModify;
|
||||
const showCancelButton = !showDeleteButton;
|
||||
const showDeleteButton = (this.props.showDelete === undefined || this.props.showDelete) && canUserModify;
|
||||
const showCancelButton = (this.props.showCancel === undefined || this.props.showCancel) && !showDeleteButton;
|
||||
// Picture snapshot - only show button when apps are maximised.
|
||||
const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show;
|
||||
const showMinimiseButton = this.props.showMinimise && this.props.show;
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import classNames from 'classnames';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
|
||||
export default function(props) {
|
||||
const isWarning = props.status === "warning";
|
||||
|
@ -35,5 +36,10 @@ export default function(props) {
|
|||
_t("All devices for this user are trusted") :
|
||||
_t("All devices in this encrypted room are trusted");
|
||||
}
|
||||
return (<div className={e2eIconClasses} title={e2eTitle} />);
|
||||
const icon = (<div className={e2eIconClasses} title={e2eTitle} />);
|
||||
if (props.onClick) {
|
||||
return (<AccessibleButton onClick={props.onClick}>{ icon }</AccessibleButton>);
|
||||
} else {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -327,6 +327,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
top: y,
|
||||
eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined,
|
||||
collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined,
|
||||
e2eInfoCallback: () => this.onCryptoClicked(),
|
||||
onFinished: function() {
|
||||
self.setState({menu: false});
|
||||
},
|
||||
|
@ -773,29 +774,31 @@ module.exports.haveTileForEvent = function(e) {
|
|||
|
||||
function E2ePadlockUndecryptable(props) {
|
||||
return (
|
||||
<E2ePadlock title={_t("Undecryptable")} icon="undecryptable" />
|
||||
<E2ePadlock title={_t("Undecryptable")} icon="undecryptable" {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlockUnverified(props) {
|
||||
return (
|
||||
<E2ePadlock title={_t("Encrypted by an unverified device")} icon="unverified" />
|
||||
<E2ePadlock title={_t("Encrypted by an unverified device")} icon="unverified" {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlockUnencrypted(props) {
|
||||
return (
|
||||
<E2ePadlock title={_t("Unencrypted message")} icon="unencrypted" />
|
||||
<E2ePadlock title={_t("Unencrypted message")} icon="unencrypted" {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlock(props) {
|
||||
if (SettingsStore.getValue("alwaysShowEncryptionIcons")) {
|
||||
return <div
|
||||
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${props.icon}`}
|
||||
title={props.title} onClick={props.onClick} />;
|
||||
return (<div
|
||||
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${props.icon}`}
|
||||
title={props.title} onClick={props.onClick} />);
|
||||
} else {
|
||||
return <div className="mx_EventTile_e2eIcon mx_EventTile_e2eIcon_hidden" onClick={props.onClick} />;
|
||||
return (<div
|
||||
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_hidden mx_EventTile_e2eIcon_${props.icon}`}
|
||||
onClick={props.onClick} />);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ export default class MemberDeviceInfo extends React.Component {
|
|||
mx_MemberDeviceInfo_icon_unverified: this.props.device.isUnverified(),
|
||||
});
|
||||
const indicator = (<div className={iconClasses} />);
|
||||
const deviceName = this.props.device.ambiguous ?
|
||||
const deviceName = (this.props.device.ambiguous || this.props.showDeviceId) ?
|
||||
(this.props.device.getDisplayName() ? this.props.device.getDisplayName() : "") + " (" + this.props.device.deviceId + ")" :
|
||||
this.props.device.getDisplayName();
|
||||
|
||||
|
|
|
@ -941,6 +941,8 @@ module.exports = withMatrixClient(React.createClass({
|
|||
}
|
||||
|
||||
let roomMemberDetails = null;
|
||||
let e2eIconElement;
|
||||
|
||||
if (this.props.member.roomId) { // is in room
|
||||
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
||||
roomMemberDetails = <div>
|
||||
|
@ -959,6 +961,11 @@ module.exports = withMatrixClient(React.createClass({
|
|||
{statusLabel}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
const isEncrypted = this.props.matrixClient.isRoomEncrypted(this.props.member.roomId);
|
||||
if (this.state.e2eStatus && isEncrypted) {
|
||||
e2eIconElement = (<E2EIcon status={this.state.e2eStatus} isUser={true} />);
|
||||
}
|
||||
}
|
||||
|
||||
const avatarUrl = this.props.member.getMxcAvatarUrl();
|
||||
|
@ -967,7 +974,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
const httpUrl = this.props.matrixClient.mxcUrlToHttp(avatarUrl, 800, 800);
|
||||
avatarElement = <div className="mx_MemberInfo_avatar">
|
||||
<img src={httpUrl} />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
||||
|
@ -979,7 +986,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}>
|
||||
<img src={require("../../../../res/img/minimise.svg")} width="10" height="16" className="mx_filterFlipColor" alt={_t('Close')} />
|
||||
</AccessibleButton>
|
||||
{ this.state.e2eStatus ? <E2EIcon status={this.state.e2eStatus} isUser={true} /> : undefined }
|
||||
{ e2eIconElement }
|
||||
<EmojiText element="h2">{ memberName }</EmojiText>
|
||||
</div>
|
||||
{ avatarElement }
|
||||
|
|
109
src/components/views/rooms/RoomBreadcrumbs.js
Normal file
109
src/components/views/rooms/RoomBreadcrumbs.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
import React from "react";
|
||||
import dis from "../../../dispatcher";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import RoomAvatar from '../avatars/RoomAvatar';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const MAX_ROOMS = 20;
|
||||
|
||||
export default class RoomBreadcrumbs extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {rooms: []};
|
||||
this.onAction = this.onAction.bind(this);
|
||||
this._previousRoomId = null;
|
||||
this._dispatcherRef = null;
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._dispatcherRef = dis.register(this.onAction);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
dis.unregister(this._dispatcherRef);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const rooms = this.state.rooms.slice();
|
||||
if (rooms.length) {
|
||||
const {room, animated} = rooms[0];
|
||||
if (!animated) {
|
||||
rooms[0] = {room, animated: true};
|
||||
setTimeout(() => this.setState({rooms}), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onAction(payload) {
|
||||
switch (payload.action) {
|
||||
case 'view_room':
|
||||
if (this._previousRoomId) {
|
||||
this._appendRoomId(this._previousRoomId);
|
||||
}
|
||||
this._previousRoomId = payload.room_id;
|
||||
}
|
||||
}
|
||||
|
||||
_appendRoomId(roomId) {
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
const rooms = this.state.rooms.slice();
|
||||
const existingIdx = rooms.findIndex((r) => r.room.roomId === room.roomId);
|
||||
if (existingIdx !== -1) {
|
||||
rooms.splice(existingIdx, 1);
|
||||
}
|
||||
rooms.splice(0, 0, {room, animated: false});
|
||||
if (rooms.length > MAX_ROOMS) {
|
||||
rooms.splice(MAX_ROOMS, rooms.length - MAX_ROOMS);
|
||||
}
|
||||
this.setState({rooms});
|
||||
}
|
||||
|
||||
_viewRoom(room) {
|
||||
dis.dispatch({action: "view_room", room_id: room.roomId});
|
||||
}
|
||||
|
||||
render() {
|
||||
// check for collapsed here and
|
||||
// not at parent so we keep
|
||||
// rooms in our state
|
||||
// when collapsing and expanding
|
||||
if (this.props.collapsed) {
|
||||
return null;
|
||||
}
|
||||
const rooms = this.state.rooms;
|
||||
const avatars = rooms.map(({room, animated}, i) => {
|
||||
const isFirst = i === 0;
|
||||
const classes = classNames({
|
||||
"mx_RoomBreadcrumbs_preAnimate": isFirst && !animated,
|
||||
"mx_RoomBreadcrumbs_animate": isFirst,
|
||||
});
|
||||
return (
|
||||
<AccessibleButton className={classes} key={room.roomId} title={room.name} onClick={() => this._viewRoom(room)}>
|
||||
<RoomAvatar room={room} width={32} height={32} />
|
||||
</AccessibleButton>
|
||||
);
|
||||
});
|
||||
return (<div className="mx_RoomBreadcrumbs">{ avatars }</div>);
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ import {CancelButton} from './SimpleRoomHeader';
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
||||
import E2EIcon from './E2EIcon';
|
||||
import * as cryptodevices from '../../../cryptodevices';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomHeader',
|
||||
|
@ -145,6 +146,12 @@ module.exports = React.createClass({
|
|||
return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0);
|
||||
},
|
||||
|
||||
_onShowDevicesClick: function() {
|
||||
if (this.props.e2eStatus === "warning") {
|
||||
cryptodevices.showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
|
@ -156,7 +163,7 @@ module.exports = React.createClass({
|
|||
let pinnedEventsButton = null;
|
||||
|
||||
const e2eIcon = this.props.e2eStatus ?
|
||||
<E2EIcon status={this.props.e2eStatus} /> :
|
||||
<E2EIcon status={this.props.e2eStatus} onClick={this._onShowDevicesClick} /> :
|
||||
undefined;
|
||||
|
||||
if (this.props.onCancelClick) {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import Timer from "../../../utils/Timer";
|
||||
|
||||
const React = require("react");
|
||||
const ReactDOM = require("react-dom");
|
||||
|
@ -41,6 +42,7 @@ import {Resizer} from '../../../resizer';
|
|||
import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2';
|
||||
const HIDE_CONFERENCE_CHANS = true;
|
||||
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
|
||||
const HOVER_MOVE_TIMEOUT = 1000;
|
||||
|
||||
function labelForTagName(tagName) {
|
||||
if (tagName.startsWith('u.')) return tagName.slice(2);
|
||||
|
@ -73,6 +75,7 @@ module.exports = React.createClass({
|
|||
|
||||
getInitialState: function() {
|
||||
|
||||
this._hoverClearTimer = null;
|
||||
this._subListRefs = {
|
||||
// key => RoomSubList ref
|
||||
};
|
||||
|
@ -95,7 +98,7 @@ module.exports = React.createClass({
|
|||
// update overflow indicators
|
||||
this._checkSubListsOverflow();
|
||||
// don't store height for collapsed sublists
|
||||
if(!this.collapsedState[key]) {
|
||||
if (!this.collapsedState[key]) {
|
||||
this.subListSizes[key] = size;
|
||||
window.localStorage.setItem("mx_roomlist_sizes",
|
||||
JSON.stringify(this.subListSizes));
|
||||
|
@ -357,11 +360,32 @@ module.exports = React.createClass({
|
|||
this.forceUpdate();
|
||||
},
|
||||
|
||||
onMouseEnter: function(ev) {
|
||||
this.setState({hover: true});
|
||||
onMouseMove: async function(ev) {
|
||||
if (!this._hoverClearTimer) {
|
||||
this.setState({hover: true});
|
||||
this._hoverClearTimer = new Timer(HOVER_MOVE_TIMEOUT);
|
||||
this._hoverClearTimer.start();
|
||||
let finished = true;
|
||||
try {
|
||||
await this._hoverClearTimer.finished();
|
||||
} catch (err) {
|
||||
finished = false;
|
||||
}
|
||||
this._hoverClearTimer = null;
|
||||
if (finished) {
|
||||
this.setState({hover: false});
|
||||
this._delayedRefreshRoomList();
|
||||
}
|
||||
} else {
|
||||
this._hoverClearTimer.restart();
|
||||
}
|
||||
},
|
||||
|
||||
onMouseLeave: function(ev) {
|
||||
if (this._hoverClearTimer) {
|
||||
this._hoverClearTimer.abort();
|
||||
this._hoverClearTimer = null;
|
||||
}
|
||||
this.setState({hover: false});
|
||||
|
||||
// Refresh the room list just in case the user missed something.
|
||||
|
@ -774,7 +798,7 @@ module.exports = React.createClass({
|
|||
|
||||
return (
|
||||
<div ref={this._collectResizeContainer} className="mx_RoomList"
|
||||
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
onMouseMove={this.onMouseMove} onMouseLeave={this.onMouseLeave}>
|
||||
{ subListComponents }
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -39,6 +39,7 @@ export default class RoomRecoveryReminder extends React.PureComponent {
|
|||
loading: true,
|
||||
error: null,
|
||||
backupInfo: null,
|
||||
notNowClicked: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -77,6 +78,10 @@ export default class RoomRecoveryReminder extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
onOnNotNowClick = () => {
|
||||
this.setState({notNowClicked: true});
|
||||
}
|
||||
|
||||
onDontAskAgainClick = () => {
|
||||
// When you choose "Don't ask again" from the room reminder, we show a
|
||||
// dialog to confirm the choice.
|
||||
|
@ -104,46 +109,54 @@ export default class RoomRecoveryReminder extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
if (this.state.loading) {
|
||||
// If there was an error loading just don't display the banner: we'll try again
|
||||
// next time the user switchs to the room.
|
||||
if (this.state.error || this.state.loading || this.state.notNowClicked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
||||
|
||||
let body;
|
||||
if (this.state.error) {
|
||||
body = <div className="error">
|
||||
{_t("Unable to load key backup status")}
|
||||
</div>;
|
||||
} else if (this.state.backupInfo) {
|
||||
// A key backup exists for this account, but we're not using it.
|
||||
body = <div>
|
||||
<p>{_t(
|
||||
"Secure Key Backup should be active on all of your devices to avoid " +
|
||||
"losing access to your encrypted messages.",
|
||||
)}</p>
|
||||
</div>;
|
||||
let setupCaption;
|
||||
if (this.state.backupInfo) {
|
||||
setupCaption = _t("Use Key Backup");
|
||||
} else {
|
||||
body = _t(
|
||||
"Securely back up your decryption keys to the server to make sure " +
|
||||
"you'll always be able to read your encrypted messages.",
|
||||
);
|
||||
setupCaption = _t("Start using Key Backup");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_RoomRecoveryReminder">
|
||||
<div className="mx_RoomRecoveryReminder_header">{_t(
|
||||
"Don't risk losing your encrypted messages!",
|
||||
"Never lose encrypted messages",
|
||||
)}</div>
|
||||
<div className="mx_RoomRecoveryReminder_body">{body}</div>
|
||||
<div className="mx_RoomRecoveryReminder_body">
|
||||
<p>{_t(
|
||||
"Messages in this room are secured with end-to-end " +
|
||||
"encryption. Only you and the recipient(s) have the " +
|
||||
"keys to read these messages.",
|
||||
)}</p>
|
||||
<p>{_t(
|
||||
"Securely back up your keys to avoid losing them. " +
|
||||
"<a>Learn more.</a>", {},
|
||||
{
|
||||
// TODO: We don't have this link yet: this will prevent the translators
|
||||
// having to re-translate the string when we do.
|
||||
a: sub => '',
|
||||
},
|
||||
)}</p>
|
||||
</div>
|
||||
<div className="mx_RoomRecoveryReminder_buttons">
|
||||
<AccessibleButton className="mx_RoomRecoveryReminder_button"
|
||||
onClick={this.onSetupClick}>
|
||||
{_t("Activate Secure Key Backup")}
|
||||
{setupCaption}
|
||||
</AccessibleButton>
|
||||
<p><AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
|
||||
onClick={this.onOnNotNowClick}>
|
||||
{ _t("Not now") }
|
||||
</AccessibleButton></p>
|
||||
<p><AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
|
||||
onClick={this.onDontAskAgainClick}>
|
||||
{ _t("No thanks, I'll download a copy of my decryption keys before I log out") }
|
||||
{ _t("Don't ask me again") }
|
||||
</AccessibleButton></p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -108,13 +108,6 @@ module.exports = React.createClass({
|
|||
return statusUser._unstable_statusMessage;
|
||||
},
|
||||
|
||||
onRoomTimeline: function(ev, room) {
|
||||
if (room !== this.props.room) return;
|
||||
this.setState({
|
||||
notificationCount: this.props.room.getUnreadNotificationCount(),
|
||||
});
|
||||
},
|
||||
|
||||
onRoomName: function(room) {
|
||||
if (room !== this.props.room) return;
|
||||
this.setState({
|
||||
|
@ -159,7 +152,6 @@ module.exports = React.createClass({
|
|||
|
||||
componentWillMount: function() {
|
||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
||||
ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
|
@ -179,7 +171,6 @@ module.exports = React.createClass({
|
|||
const cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||
}
|
||||
ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||
|
@ -306,7 +297,7 @@ module.exports = React.createClass({
|
|||
|
||||
render: function() {
|
||||
const isInvite = this.props.room.getMyMembership() === "invite";
|
||||
const notificationCount = this.state.notificationCount;
|
||||
const notificationCount = this.props.notificationCount;
|
||||
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
|
||||
|
||||
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();
|
||||
|
|
|
@ -226,6 +226,7 @@ export default class Stickerpicker extends React.Component {
|
|||
showTitle={false}
|
||||
showMinimise={true}
|
||||
showDelete={false}
|
||||
showCancel={false}
|
||||
showPopout={false}
|
||||
onMinimiseClick={this._onHideStickersClick}
|
||||
handleMinimisePointerEvents={true}
|
||||
|
|
|
@ -111,10 +111,10 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
Modal.createTrackedDialog('Delete Backup', '', QuestionDialog, {
|
||||
title: _t('Delete Backup'),
|
||||
description: _t(
|
||||
"Delete your backed up encryption keys from the server? " +
|
||||
"You will no longer be able to use your recovery key to read encrypted message history",
|
||||
"Are you sure? You will lose your encrypted messages if your " +
|
||||
"keys are not backed up properly.",
|
||||
),
|
||||
button: _t('Delete backup'),
|
||||
button: _t('Delete Backup'),
|
||||
danger: true,
|
||||
onFinished: (proceed) => {
|
||||
if (!proceed) return;
|
||||
|
@ -135,6 +135,10 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
render() {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
const encryptedMessageAreEncrypted = _t(
|
||||
"Encrypted messages are secured with end-to-end encryption. " +
|
||||
"Only you and the recipient(s) have the keys to read these messages.",
|
||||
);
|
||||
|
||||
if (this.state.error) {
|
||||
return (
|
||||
|
@ -145,14 +149,25 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
} else if (this.state.loading) {
|
||||
return <Spinner />;
|
||||
} else if (this.state.backupInfo) {
|
||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||
let clientBackupStatus;
|
||||
let restoreButtonCaption = _t("Restore from Backup");
|
||||
|
||||
if (MatrixClientPeg.get().getKeyBackupEnabled()) {
|
||||
clientBackupStatus = _t("This device is using key backup");
|
||||
clientBackupStatus = <div>
|
||||
<p>{encryptedMessageAreEncrypted}</p>
|
||||
<p>{_t("This device is backing up your keys. ")}<EmojiText>✅</EmojiText></p>
|
||||
</div>;
|
||||
} else {
|
||||
clientBackupStatus = _t(
|
||||
"This device is <b>not</b> using key backup. Restore the backup to start using it.", {},
|
||||
{b: x => <b>{x}</b>},
|
||||
);
|
||||
clientBackupStatus = <div>
|
||||
<p>{encryptedMessageAreEncrypted}</p>
|
||||
<p>{_t(
|
||||
"This device is <b>not backing up your keys</b>.", {},
|
||||
{b: sub => <b>{sub}</b>},
|
||||
)}</p>
|
||||
<p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
|
||||
</div>;
|
||||
restoreButtonCaption = _t("Use key backup");
|
||||
}
|
||||
|
||||
let uploadStatus;
|
||||
|
@ -243,18 +258,25 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
</details>
|
||||
<p>
|
||||
<AccessibleButton kind="primary" onClick={this._restoreBackup}>
|
||||
{ _t("Restore backup") }
|
||||
{restoreButtonCaption}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind="danger" onClick={this._deleteBackup}>
|
||||
{ _t("Delete backup") }
|
||||
{ _t("Delete Backup") }
|
||||
</AccessibleButton>
|
||||
</p>
|
||||
</div>;
|
||||
} else {
|
||||
return <div>
|
||||
{_t("No backup is present")}<br /><br />
|
||||
<div>
|
||||
<p>{_t(
|
||||
"Your keys are <b>not being backed up from this device</b>.", {},
|
||||
{b: sub => <b>{sub}</b>},
|
||||
)}</p>
|
||||
<p>{encryptedMessageAreEncrypted}</p>
|
||||
<p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
|
||||
</div>
|
||||
<AccessibleButton kind="primary" onClick={this._startNewBackup}>
|
||||
{ _t("Start a new backup") }
|
||||
{ _t("Start using Key Backup") }
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -270,6 +270,7 @@
|
|||
"Failed to join room": "Failed to join room",
|
||||
"Message Pinning": "Message Pinning",
|
||||
"Custom user status messages": "Custom user status messages",
|
||||
"Show recent room avatars above the room list (refresh to apply changes)": "Show recent room avatars above the room list (refresh to apply changes)",
|
||||
"Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)",
|
||||
"Render simple counters in room header": "Render simple counters in room header",
|
||||
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||
|
@ -439,11 +440,14 @@
|
|||
"Disable Notifications": "Disable Notifications",
|
||||
"Enable Notifications": "Enable Notifications",
|
||||
"Delete Backup": "Delete Backup",
|
||||
"Delete your backed up encryption keys from the server? You will no longer be able to use your recovery key to read encrypted message history": "Delete your backed up encryption keys from the server? You will no longer be able to use your recovery key to read encrypted message history",
|
||||
"Delete backup": "Delete backup",
|
||||
"Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.",
|
||||
"Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
|
||||
"Unable to load key backup status": "Unable to load key backup status",
|
||||
"This device is using key backup": "This device is using key backup",
|
||||
"This device is <b>not</b> using key backup. Restore the backup to start using it.": "This device is <b>not</b> using key backup. Restore the backup to start using it.",
|
||||
"Restore from Backup": "Restore from Backup",
|
||||
"This device is backing up your keys. ": "This device is backing up your keys. ",
|
||||
"This device is <b>not backing up your keys</b>.": "This device is <b>not backing up your keys</b>.",
|
||||
"Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.",
|
||||
"Use key backup": "Use key backup",
|
||||
"Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...",
|
||||
"All keys backed up": "All keys backed up",
|
||||
"Backup has a signature from <verify>unknown</verify> device with ID %(deviceId)s.": "Backup has a signature from <verify>unknown</verify> device with ID %(deviceId)s.",
|
||||
|
@ -457,9 +461,8 @@
|
|||
"Advanced": "Advanced",
|
||||
"Backup version: ": "Backup version: ",
|
||||
"Algorithm: ": "Algorithm: ",
|
||||
"Restore backup": "Restore backup",
|
||||
"No backup is present": "No backup is present",
|
||||
"Start a new backup": "Start a new backup",
|
||||
"Your keys are <b>not being backed up from this device</b>.": "Your keys are <b>not being backed up from this device</b>.",
|
||||
"Start using Key Backup": "Start using Key Backup",
|
||||
"Error saving email notification preferences": "Error saving email notification preferences",
|
||||
"An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.",
|
||||
"Keywords": "Keywords",
|
||||
|
@ -785,11 +788,12 @@
|
|||
"You are trying to access a room.": "You are trying to access a room.",
|
||||
"<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!",
|
||||
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
|
||||
"Secure Key Backup should be active on all of your devices to avoid losing access to your encrypted messages.": "Secure Key Backup should be active on all of your devices to avoid losing access to your encrypted messages.",
|
||||
"Securely back up your decryption keys to the server to make sure you'll always be able to read your encrypted messages.": "Securely back up your decryption keys to the server to make sure you'll always be able to read your encrypted messages.",
|
||||
"Don't risk losing your encrypted messages!": "Don't risk losing your encrypted messages!",
|
||||
"Activate Secure Key Backup": "Activate Secure Key Backup",
|
||||
"No thanks, I'll download a copy of my decryption keys before I log out": "No thanks, I'll download a copy of my decryption keys before I log out",
|
||||
"Use Key Backup": "Use Key Backup",
|
||||
"Never lose encrypted messages": "Never lose encrypted messages",
|
||||
"Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
|
||||
"Securely back up your keys to avoid losing them. <a>Learn more.</a>": "Securely back up your keys to avoid losing them. <a>Learn more.</a>",
|
||||
"Not now": "Not now",
|
||||
"Don't ask me again": "Don't ask me again",
|
||||
"Add a topic": "Add a topic",
|
||||
"This room is using an unstable room version. If you aren't expecting this, please upgrade the room.": "This room is using an unstable room version. If you aren't expecting this, please upgrade the room.",
|
||||
"Click here to upgrade to the latest room version.": "Click here to upgrade to the latest room version.",
|
||||
|
@ -1061,6 +1065,7 @@
|
|||
"Verify by comparing a short text string.": "Verify by comparing a short text string.",
|
||||
"Begin Verifying": "Begin Verifying",
|
||||
"Waiting for partner to accept...": "Waiting for partner to accept...",
|
||||
"Nothing appearing? Not all clients support interactive verification yet. <button>Use legacy verification</button>.": "Nothing appearing? Not all clients support interactive verification yet. <button>Use legacy verification</button>.",
|
||||
"Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...",
|
||||
"Use two-way text verification": "Use two-way text verification",
|
||||
"To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:",
|
||||
|
@ -1100,11 +1105,10 @@
|
|||
"Clear cache and resync": "Clear cache and resync",
|
||||
"Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
|
||||
"Updating Riot": "Updating Riot",
|
||||
"When you log out, you'll lose your secure message history. To prevent this, set up a recovery method.": "When you log out, you'll lose your secure message history. To prevent this, set up a recovery method.",
|
||||
"Alternatively, advanced users can also manually export encryption keys in <a>Settings</a> before logging out.": "Alternatively, advanced users can also manually export encryption keys in <a>Settings</a> before logging out.",
|
||||
"Set a Recovery Method": "Set a Recovery Method",
|
||||
"I understand, log out without": "I understand, log out without",
|
||||
"When signing in again, you can access encrypted chat history by restoring your key backup. You'll need your recovery passphrase or, if you didn't set a recovery passphrase, your recovery key (that you downloaded).": "When signing in again, you can access encrypted chat history by restoring your key backup. You'll need your recovery passphrase or, if you didn't set a recovery passphrase, your recovery key (that you downloaded).",
|
||||
"I don't want my encrypted messages": "I don't want my encrypted messages",
|
||||
"Manually export keys": "Manually export keys",
|
||||
"You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages",
|
||||
"Are you sure you want to sign out?": "Are you sure you want to sign out?",
|
||||
"Thanks for testing the Riot Redesign. If you run into any bugs or visual issues, please let us know on GitHub.": "Thanks for testing the Riot Redesign. If you run into any bugs or visual issues, please let us know on GitHub.",
|
||||
"To help avoid duplicate issues, please <existingIssuesLink>view existing issues</existingIssuesLink> first (and add a +1) or <newIssueLink>create a new issue</newIssueLink> if you can't find it.": "To help avoid duplicate issues, please <existingIssuesLink>view existing issues</existingIssuesLink> first (and add a +1) or <newIssueLink>create a new issue</newIssueLink> if you can't find it.",
|
||||
"Report bugs & give feedback": "Report bugs & give feedback",
|
||||
|
@ -1171,6 +1175,7 @@
|
|||
"Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!",
|
||||
"Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys",
|
||||
"Enter Recovery Passphrase": "Enter Recovery Passphrase",
|
||||
"<b>Warning</b>: you should only set up key backup from a trusted computer.": "<b>Warning</b>: you should only set up key backup from a trusted computer.",
|
||||
"Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.",
|
||||
"Next": "Next",
|
||||
"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>",
|
||||
|
@ -1216,6 +1221,7 @@
|
|||
"Set status": "Set status",
|
||||
"Set a new status...": "Set a new status...",
|
||||
"View Community": "View Community",
|
||||
"Hide": "Hide",
|
||||
"Login": "Login",
|
||||
"powered by Matrix": "powered by Matrix",
|
||||
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
|
||||
|
@ -1487,19 +1493,19 @@
|
|||
"Import": "Import",
|
||||
"Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.",
|
||||
"Keep going...": "Keep going...",
|
||||
"Secure your encrypted message history with a Recovery Passphrase.": "Secure your encrypted message history with a Recovery Passphrase.",
|
||||
"You'll need it if you log out or lose access to this device.": "You'll need it if you log out or lose access to this device.",
|
||||
"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.",
|
||||
"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...",
|
||||
"If you don't want encrypted message history to be available on other devices, <button>opt out</button>.": "If you don't want encrypted message history to be available on other devices, <button>opt out</button>.",
|
||||
"Or, if you don't want to create a Recovery Passphrase, skip this step and <button>download a recovery key</button>.": "Or, if you don't want to create a Recovery Passphrase, skip this step and <button>download a recovery key</button>.",
|
||||
"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.",
|
||||
"Type in your Recovery Passphrase to confirm you remember it. If it helps, add it to your password manager or store it somewhere safe.": "Type in your Recovery Passphrase to confirm you remember it. If it helps, add it to your password manager or store it somewhere safe.",
|
||||
"Please enter your passphrase a second time to confirm.": "Please enter your passphrase a second time to confirm.",
|
||||
"Repeat your passphrase...": "Repeat your passphrase...",
|
||||
"As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.": "As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.",
|
||||
"As a safety net, you can use it to restore your encrypted message history.": "As a safety net, you can use it to restore your encrypted message history.",
|
||||
"Make a copy of this Recovery Key and keep it safe.": "Make a copy of this Recovery Key and keep it safe.",
|
||||
"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.",
|
||||
"Keep your recovery key somewhere very secure, like a password manager (or a safe)": "Keep your recovery key somewhere very secure, like a password manager (or a safe)",
|
||||
"Your Recovery Key": "Your Recovery Key",
|
||||
"Copy to clipboard": "Copy to clipboard",
|
||||
"Download": "Download",
|
||||
|
@ -1508,15 +1514,16 @@
|
|||
"<b>Print it</b> and store it somewhere safe": "<b>Print it</b> and store it somewhere safe",
|
||||
"<b>Save it</b> on a USB key or backup drive": "<b>Save it</b> on a USB key or backup drive",
|
||||
"<b>Copy it</b> to your personal cloud storage": "<b>Copy it</b> to your personal cloud storage",
|
||||
"Your encryption keys are now being backed up in the background to your Homeserver. The initial backup could take several minutes. You can view key backup upload progress in Settings.": "Your encryption keys are now being backed up in the background to your Homeserver. The initial backup could take several minutes. You can view key backup upload progress in Settings.",
|
||||
"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).",
|
||||
"Okay": "Okay",
|
||||
"Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.",
|
||||
"Set up Secure Message Recovery": "Set up Secure Message Recovery",
|
||||
"Create a Recovery Passphrase": "Create a Recovery Passphrase",
|
||||
"Confirm Recovery Passphrase": "Confirm Recovery Passphrase",
|
||||
"Recovery Key": "Recovery Key",
|
||||
"Secure your backup with a passphrase": "Secure your backup with a passphrase",
|
||||
"Confirm your passphrase": "Confirm your passphrase",
|
||||
"Recovery key": "Recovery key",
|
||||
"Keep it safe": "Keep it safe",
|
||||
"Starting backup...": "Starting backup...",
|
||||
"Backup Started": "Backup Started",
|
||||
"Success!": "Success!",
|
||||
"Create Key Backup": "Create Key Backup",
|
||||
"Unable to create key backup": "Unable to create key backup",
|
||||
"Retry": "Retry",
|
||||
|
|
|
@ -20,54 +20,28 @@ limitations under the License.
|
|||
* to update the interface once for all of them.
|
||||
*
|
||||
* Note that the function must not take arguments, since the args
|
||||
* could be different for each invocarion of the function.
|
||||
* could be different for each invocation of the function.
|
||||
*
|
||||
* The returned function has a 'cancelPendingCall' property which can be called
|
||||
* on unmount or similar to cancel any pending update.
|
||||
*/
|
||||
module.exports = function(f, minIntervalMs) {
|
||||
this.lastCall = 0;
|
||||
this.scheduledCall = undefined;
|
||||
|
||||
const self = this;
|
||||
const wrapper = function() {
|
||||
const now = Date.now();
|
||||
import { throttle } from "lodash";
|
||||
|
||||
if (self.lastCall < now - minIntervalMs) {
|
||||
f.apply(this);
|
||||
// get the time again now the function has finished, so if it
|
||||
// took longer than the delay time to execute, it doesn't
|
||||
// immediately become eligible to run again.
|
||||
self.lastCall = Date.now();
|
||||
} else if (self.scheduledCall === undefined) {
|
||||
self.scheduledCall = setTimeout(
|
||||
() => {
|
||||
self.scheduledCall = undefined;
|
||||
f.apply(this);
|
||||
// get time again as per above
|
||||
self.lastCall = Date.now();
|
||||
},
|
||||
(self.lastCall + minIntervalMs) - now,
|
||||
);
|
||||
}
|
||||
export default function ratelimitedfunc(fn, time) {
|
||||
const throttledFn = throttle(fn, time, {
|
||||
leading: true,
|
||||
trailing: true,
|
||||
});
|
||||
const _bind = throttledFn.bind;
|
||||
throttledFn.bind = function() {
|
||||
const boundFn = _bind.apply(throttledFn, arguments);
|
||||
boundFn.cancelPendingCall = throttledFn.cancelPendingCall;
|
||||
return boundFn;
|
||||
};
|
||||
|
||||
// add the cancelPendingCall property
|
||||
wrapper.cancelPendingCall = function() {
|
||||
if (self.scheduledCall) {
|
||||
clearTimeout(self.scheduledCall);
|
||||
self.scheduledCall = undefined;
|
||||
}
|
||||
throttledFn.cancelPendingCall = function() {
|
||||
throttledFn.cancel();
|
||||
};
|
||||
|
||||
// make sure that cancelPendingCall is copied when react rebinds the
|
||||
// wrapper
|
||||
const _bind = wrapper.bind;
|
||||
wrapper.bind = function() {
|
||||
const rebound = _bind.apply(this, arguments);
|
||||
rebound.cancelPendingCall = wrapper.cancelPendingCall;
|
||||
return rebound;
|
||||
};
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
return throttledFn;
|
||||
}
|
||||
|
|
|
@ -99,6 +99,12 @@ export const SETTINGS = {
|
|||
default: false,
|
||||
controller: new CustomStatusController(),
|
||||
},
|
||||
"feature_room_breadcrumbs": {
|
||||
isFeature: true,
|
||||
displayName: _td("Show recent room avatars above the room list (refresh to apply changes)"),
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_custom_tags": {
|
||||
isFeature: true,
|
||||
displayName: _td("Group & filter rooms by custom tags (refresh to apply changes)"),
|
||||
|
|
Loading…
Reference in a new issue