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/_DevtoolsDialog.scss";
|
||||||
@import "./views/dialogs/_EncryptedEventDialog.scss";
|
@import "./views/dialogs/_EncryptedEventDialog.scss";
|
||||||
@import "./views/dialogs/_GroupAddressPicker.scss";
|
@import "./views/dialogs/_GroupAddressPicker.scss";
|
||||||
|
@import "./views/dialogs/_IncomingSasDialog.scss";
|
||||||
@import "./views/dialogs/_RestoreKeyBackupDialog.scss";
|
@import "./views/dialogs/_RestoreKeyBackupDialog.scss";
|
||||||
@import "./views/dialogs/_RoomSettingsDialog.scss";
|
@import "./views/dialogs/_RoomSettingsDialog.scss";
|
||||||
@import "./views/dialogs/_RoomUpgradeDialog.scss";
|
@import "./views/dialogs/_RoomUpgradeDialog.scss";
|
||||||
|
@ -126,6 +127,7 @@
|
||||||
@import "./views/rooms/_PinnedEventsPanel.scss";
|
@import "./views/rooms/_PinnedEventsPanel.scss";
|
||||||
@import "./views/rooms/_PresenceLabel.scss";
|
@import "./views/rooms/_PresenceLabel.scss";
|
||||||
@import "./views/rooms/_ReplyPreview.scss";
|
@import "./views/rooms/_ReplyPreview.scss";
|
||||||
|
@import "./views/rooms/_RoomBreadcrumbs.scss";
|
||||||
@import "./views/rooms/_RoomDropTarget.scss";
|
@import "./views/rooms/_RoomDropTarget.scss";
|
||||||
@import "./views/rooms/_RoomHeader.scss";
|
@import "./views/rooms/_RoomHeader.scss";
|
||||||
@import "./views/rooms/_RoomList.scss";
|
@import "./views/rooms/_RoomList.scss";
|
||||||
|
|
|
@ -28,8 +28,8 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TabbedView_tabLabels {
|
.mx_TabbedView_tabLabels {
|
||||||
width: 150px;
|
width: 170px;
|
||||||
max-width: 150px;
|
max-width: 170px;
|
||||||
color: $tab-label-fg-color;
|
color: $tab-label-fg-color;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,8 @@ limitations under the License.
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
min-height: 24px; // use min-height instead of height to allow the label to overflow a bit
|
||||||
min-height: 20px; // use min-height instead of height to allow the label to overflow a bit
|
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -55,8 +54,8 @@ limitations under the License.
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
margin-right: 9px;
|
margin-right: 9px;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
width: 14px;
|
width: 16px;
|
||||||
height: 14px;
|
height: 16px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,9 +63,9 @@ limitations under the License.
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: $tab-label-icon-bg-color;
|
background-color: $tab-label-icon-bg-color;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-size: 14px;
|
mask-size: 16px;
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 18px;
|
height: 22px;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
content: '';
|
content: '';
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
@ -81,7 +80,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TabbedView_tabPanel {
|
.mx_TabbedView_tabPanel {
|
||||||
margin-left: 220px; // 150px sidebar + 70px padding
|
margin-left: 240px; // 170px sidebar + 70px padding
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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_SettingsDialog {
|
||||||
.mx_Dialog {
|
.mx_Dialog {
|
||||||
max-width: 900px;
|
max-width: 1000px;
|
||||||
width: 80%;
|
width: 90%;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
@ -30,7 +30,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_TabbedView .mx_SettingsTab {
|
.mx_TabbedView .mx_SettingsTab {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
min-width: 550px;
|
min-width: 580px;
|
||||||
padding-right: 130px;
|
padding-right: 130px;
|
||||||
|
|
||||||
// Put some padding on the bottom to avoid the settings tab from
|
// Put some padding on the bottom to avoid the settings tab from
|
||||||
|
|
|
@ -28,28 +28,28 @@ limitations under the License.
|
||||||
flex-direction: column;
|
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 {
|
.mx_UnknownDeviceDialog .mx_DeviceVerifyButtons {
|
||||||
float: right;
|
flex-direction: row !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UnknownDeviceDialog .mx_Dialog_content {
|
.mx_UnknownDeviceDialog .mx_Dialog_content {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UnknownDeviceDialog .mx_MemberDeviceInfo {
|
.mx_UnknownDeviceDialog_deviceList > li {
|
||||||
float: right;
|
padding: 4px;
|
||||||
clear: both;
|
|
||||||
padding: 0px;
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UnknownDeviceDialog .mx_MemberDeviceInfo_textButton {
|
.mx_UnknownDeviceDialog_deviceList > li > * {
|
||||||
@mixin mx_DialogButton_small;
|
padding-bottom: 0;
|
||||||
background-color: $primary-bg-color;
|
|
||||||
color: $accent-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_UnknownDeviceDialog .mx_UnknownDeviceDialog_deviceList li {
|
|
||||||
height: 40px;
|
|
||||||
border-bottom: 1px solid $primary-hairline-color;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,16 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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 {
|
.mx_AppsDrawer {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +93,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTile_persistedWrapper {
|
.mx_AppTile_persistedWrapper {
|
||||||
height: 280px;
|
height: $AppsDrawerBodyHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
|
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
|
||||||
|
@ -189,7 +199,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTileBody{
|
.mx_AppTileBody{
|
||||||
height: 280px;
|
height: $AppsDrawerBodyHeight;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
@ -208,7 +218,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_AppTileBody iframe {
|
.mx_AppTileBody iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 280px;
|
height: $AppsDrawerBodyHeight;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -332,7 +342,7 @@ form.mx_Custom_Widget_Form div {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 280px;
|
height: $AppsDrawerBodyHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppLoading .mx_Spinner {
|
.mx_AppLoading .mx_Spinner {
|
||||||
|
|
|
@ -533,7 +533,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_e2eIcon {
|
.mx_EventTile_e2eIcon {
|
||||||
top: 7px;
|
top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_editButton {
|
.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 {
|
.mx_RoomTile_unread, .mx_RoomTile_highlight {
|
||||||
font-weight: 700 ! important;
|
font-weight: 700 !important;
|
||||||
|
|
||||||
.mx_RoomTile_name {
|
.mx_RoomTile_name {
|
||||||
color: $roomtile-selected-color;
|
color: $roomtile-selected-color;
|
||||||
|
@ -176,7 +176,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomTile:focus {
|
.mx_RoomTile:focus {
|
||||||
filter: none ! important;
|
filter: none !important;
|
||||||
background-color: $roomtile-focused-bg-color;
|
background-color: $roomtile-focused-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,12 @@
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Stickers_content .mx_AppTileFullWidth {
|
#mx_persistedElement_stickerPicker .mx_AppTileFullWidth {
|
||||||
border: none;
|
height: unset;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Stickers_contentPlaceholder {
|
.mx_Stickers_contentPlaceholder {
|
||||||
|
|
|
@ -21,7 +21,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SettingsTab_subheading {
|
.mx_SettingsTab_subheading {
|
||||||
font-size: 14px;
|
font-size: 16px;
|
||||||
display: block;
|
display: block;
|
||||||
font-family: $font-family;
|
font-family: $font-family;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
@ -32,7 +32,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_SettingsTab_subsectionText {
|
.mx_SettingsTab_subsectionText {
|
||||||
color: $settings-subsection-fg-color;
|
color: $settings-subsection-fg-color;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 100px 0 0; // Align with the rest of the view
|
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 {
|
.mx_SettingsTab_section .mx_SettingsFlag {
|
||||||
margin-right: 100px;
|
margin-right: 100px;
|
||||||
height: 25px;
|
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SettingsTab_section .mx_SettingsFlag .mx_SettingsFlag_label {
|
.mx_SettingsTab_section .mx_SettingsFlag .mx_SettingsFlag_label {
|
||||||
vertical-align: bottom;
|
vertical-align: middle;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
max-width: calc(100% - 48px); // Force word wrap instead of colliding with the switch
|
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 {
|
.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">
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<g fill="none" fill-rule="evenodd" stroke="#454545" stroke-linecap="round" stroke-linejoin="round">
|
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<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"/>
|
<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"
|
||||||
</g>
|
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>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 425 B After Width: | Height: | Size: 704 B |
|
@ -32,7 +32,7 @@ module.exports = {
|
||||||
return false;
|
return false;
|
||||||
} else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') {
|
} else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') {
|
||||||
return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
|
|
|
@ -240,7 +240,6 @@ export default React.createClass({
|
||||||
|
|
||||||
_renderPhasePassPhrase: function() {
|
_renderPhasePassPhrase: function() {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
|
|
||||||
let strengthMeter;
|
let strengthMeter;
|
||||||
let helpText;
|
let helpText;
|
||||||
|
@ -265,8 +264,15 @@ export default React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<p>{_t("Secure your encrypted message history with a Recovery Passphrase.")}</p>
|
<p>{_t(
|
||||||
<p>{_t("You'll need it if you log out or lose access to this device.")}</p>
|
"<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_primaryContainer">
|
||||||
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
||||||
|
@ -291,34 +297,12 @@ export default React.createClass({
|
||||||
disabled={!this._passPhraseIsValid()}
|
disabled={!this._passPhraseIsValid()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p>{_t(
|
<details>
|
||||||
"If you don't want encrypted message history to be available on other devices, "+
|
<summary>{_t("Advanced")}</summary>
|
||||||
"<button>opt out</button>.",
|
<p><button onClick={this._onSkipPassPhraseClick} >
|
||||||
{},
|
{_t("Set up with a Recovery Key")}
|
||||||
{
|
</button></p>
|
||||||
button: sub => <AccessibleButton
|
</details>
|
||||||
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>
|
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -353,9 +337,7 @@ export default React.createClass({
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return <div>
|
return <div>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Type in your Recovery Passphrase to confirm you remember it. " +
|
"Please enter your passphrase a second time to confirm.",
|
||||||
"If it helps, add it to your password manager or store it " +
|
|
||||||
"somewhere safe.",
|
|
||||||
)}</p>
|
)}</p>
|
||||||
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||||
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
||||||
|
@ -392,7 +374,13 @@ export default React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>
|
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>
|
<p>{bodyText}</p>
|
||||||
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||||
<div className="mx_CreateKeyBackupDialog_recoveryKeyHeader">
|
<div className="mx_CreateKeyBackupDialog_recoveryKeyHeader">
|
||||||
|
@ -455,10 +443,9 @@ export default React.createClass({
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return <div>
|
return <div>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Your encryption keys are now being backed up in the background " +
|
"Your keys are being backed up (the first backup could take a few minutes).",
|
||||||
"to your Homeserver. The initial backup could take several minutes. " +
|
)}</p>
|
||||||
"You can view key backup upload progress in Settings.")}</p>
|
<DialogButtons primaryButton={_t('Okay')}
|
||||||
<DialogButtons primaryButton={_t('Close')}
|
|
||||||
onPrimaryButtonClick={this._onDone}
|
onPrimaryButtonClick={this._onDone}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
/>
|
/>
|
||||||
|
@ -484,19 +471,19 @@ export default React.createClass({
|
||||||
_titleForPhase: function(phase) {
|
_titleForPhase: function(phase) {
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case PHASE_PASSPHRASE:
|
case PHASE_PASSPHRASE:
|
||||||
return _t('Create a Recovery Passphrase');
|
return _t('Secure your backup with a passphrase');
|
||||||
case PHASE_PASSPHRASE_CONFIRM:
|
case PHASE_PASSPHRASE_CONFIRM:
|
||||||
return _t('Confirm Recovery Passphrase');
|
return _t('Confirm your passphrase');
|
||||||
case PHASE_OPTOUT_CONFIRM:
|
case PHASE_OPTOUT_CONFIRM:
|
||||||
return _t('Warning!');
|
return _t('Warning!');
|
||||||
case PHASE_SHOWKEY:
|
case PHASE_SHOWKEY:
|
||||||
return _t('Recovery Key');
|
return _t('Recovery key');
|
||||||
case PHASE_KEEPITSAFE:
|
case PHASE_KEEPITSAFE:
|
||||||
return _t('Keep it safe');
|
return _t('Keep it safe');
|
||||||
case PHASE_BACKINGUP:
|
case PHASE_BACKINGUP:
|
||||||
return _t('Starting backup...');
|
return _t('Starting backup...');
|
||||||
case PHASE_DONE:
|
case PHASE_DONE:
|
||||||
return _t('Backup Started');
|
return _t('Success!');
|
||||||
default:
|
default:
|
||||||
return _t("Create Key Backup");
|
return _t("Create Key Backup");
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,36 +39,8 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupClick = async () => {
|
onSetupClick = async () => {
|
||||||
// TODO: Should change to a restore key backup flow that checks the
|
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
||||||
// recovery passphrase while at the same time also cross-signing the
|
Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
||||||
// 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,
|
|
||||||
onFinished: this.props.onFinished,
|
onFinished: this.props.onFinished,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -111,11 +83,6 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
|
||||||
} else {
|
} else {
|
||||||
content = <div>
|
content = <div>
|
||||||
{newMethodDetected}
|
{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}
|
{hackWarning}
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("Set up Secure Messages")}
|
primaryButton={_t("Set up Secure Messages")}
|
||||||
|
|
|
@ -182,6 +182,7 @@ const LeftPanel = React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const RoomList = sdk.getComponent('rooms.RoomList');
|
const RoomList = sdk.getComponent('rooms.RoomList');
|
||||||
|
const RoomBreadcrumbs = sdk.getComponent('rooms.RoomBreadcrumbs');
|
||||||
const TagPanel = sdk.getComponent('structures.TagPanel');
|
const TagPanel = sdk.getComponent('structures.TagPanel');
|
||||||
const CustomRoomTagPanel = sdk.getComponent('structures.CustomRoomTagPanel');
|
const CustomRoomTagPanel = sdk.getComponent('structures.CustomRoomTagPanel');
|
||||||
const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton');
|
const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton');
|
||||||
|
@ -215,12 +216,17 @@ const LeftPanel = React.createClass({
|
||||||
onCleared={ this.onSearchCleared }
|
onCleared={ this.onSearchCleared }
|
||||||
collapsed={this.props.collapsed} />);
|
collapsed={this.props.collapsed} />);
|
||||||
|
|
||||||
|
let breadcrumbs;
|
||||||
|
if (SettingsStore.isFeatureEnabled("feature_room_breadcrumbs")) {
|
||||||
|
breadcrumbs = (<RoomBreadcrumbs collapsed={this.props.collapsed} />);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
{ tagPanelContainer }
|
{ tagPanelContainer }
|
||||||
<aside className={"mx_LeftPanel dark-panel"} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
|
<aside className={"mx_LeftPanel dark-panel"} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
|
||||||
<TopLeftMenuButton collapsed={ this.props.collapsed } />
|
<TopLeftMenuButton collapsed={ this.props.collapsed } />
|
||||||
|
{ breadcrumbs }
|
||||||
{ searchBox }
|
{ searchBox }
|
||||||
<CallPreview ConferenceHandler={VectorConferenceHandler} />
|
<CallPreview ConferenceHandler={VectorConferenceHandler} />
|
||||||
<RoomList
|
<RoomList
|
||||||
|
|
|
@ -574,11 +574,8 @@ export default React.createClass({
|
||||||
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
||||||
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {}, 'mx_SettingsDialog');
|
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {}, 'mx_SettingsDialog');
|
||||||
|
|
||||||
// View the home page if we need something to look at
|
// View the welcome or home page if we need something to look at
|
||||||
if (!this.state.currentGroupId && !this.state.currentRoomId) {
|
this._viewSomethingBehindModal();
|
||||||
this._setPage(PageTypes.HomePage);
|
|
||||||
this.notifyNewScreen('home');
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'view_create_room':
|
case 'view_create_room':
|
||||||
|
@ -595,11 +592,8 @@ export default React.createClass({
|
||||||
config: this.props.config,
|
config: this.props.config,
|
||||||
}, 'mx_RoomDirectory_dialogWrapper');
|
}, 'mx_RoomDirectory_dialogWrapper');
|
||||||
|
|
||||||
// View the home page if we need something to look at
|
// View the welcome or home page if we need something to look at
|
||||||
if (!this.state.currentGroupId && !this.state.currentRoomId) {
|
this._viewSomethingBehindModal();
|
||||||
this._setPage(PageTypes.HomePage);
|
|
||||||
this.notifyNewScreen('home');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'view_my_groups':
|
case 'view_my_groups':
|
||||||
|
@ -825,6 +819,7 @@ export default React.createClass({
|
||||||
this.focusComposer = true;
|
this.focusComposer = true;
|
||||||
|
|
||||||
const newState = {
|
const newState = {
|
||||||
|
view: VIEWS.LOGGED_IN,
|
||||||
currentRoomId: roomInfo.room_id || null,
|
currentRoomId: roomInfo.room_id || null,
|
||||||
page_type: PageTypes.RoomView,
|
page_type: PageTypes.RoomView,
|
||||||
thirdPartyInvite: roomInfo.third_party_invite,
|
thirdPartyInvite: roomInfo.third_party_invite,
|
||||||
|
@ -887,6 +882,16 @@ export default React.createClass({
|
||||||
this.notifyNewScreen('group/' + groupId);
|
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() {
|
_viewWelcome() {
|
||||||
this.setStateForNewView({
|
this.setStateForNewView({
|
||||||
view: VIEWS.WELCOME,
|
view: VIEWS.WELCOME,
|
||||||
|
@ -1552,11 +1557,7 @@ export default React.createClass({
|
||||||
payload.room_id = roomString;
|
payload.room_id = roomString;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we can't view a room unless we're logged in
|
dis.dispatch(payload);
|
||||||
// (a guest account is fine)
|
|
||||||
if (this.state.view === VIEWS.LOGGED_IN) {
|
|
||||||
dis.dispatch(payload);
|
|
||||||
}
|
|
||||||
} else if (screen.indexOf('user/') == 0) {
|
} else if (screen.indexOf('user/') == 0) {
|
||||||
const userId = screen.substring(5);
|
const userId = screen.substring(5);
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,11 @@ module.exports = React.createClass({
|
||||||
this.protocols = null;
|
this.protocols = null;
|
||||||
|
|
||||||
this.setState({protocolsLoading: true});
|
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) => {
|
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
|
||||||
this.protocols = response;
|
this.protocols = response;
|
||||||
this.setState({protocolsLoading: false});
|
this.setState({protocolsLoading: false});
|
||||||
|
|
|
@ -145,6 +145,7 @@ const RoomSubList = React.createClass({
|
||||||
collapsed={this.props.collapsed || false}
|
collapsed={this.props.collapsed || false}
|
||||||
unread={Unread.doesRoomHaveUnreadMessages(room)}
|
unread={Unread.doesRoomHaveUnreadMessages(room)}
|
||||||
highlight={room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite}
|
highlight={room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite}
|
||||||
|
notificationCount={room.getUnreadNotificationCount()}
|
||||||
isInvite={this.props.isInvite}
|
isInvite={this.props.isInvite}
|
||||||
refreshSubList={this._updateSubListCount}
|
refreshSubList={this._updateSubListCount}
|
||||||
incomingCall={null}
|
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() {
|
_onWidgetEchoStoreUpdate: function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
showApps: this._shouldShowApps(this.state.room),
|
showApps: this._shouldShowApps(this.state.room),
|
||||||
|
@ -784,6 +793,7 @@ module.exports = React.createClass({
|
||||||
this._updateConfCallNotification();
|
this._updateConfCallNotification();
|
||||||
this._updateDMState();
|
this._updateDMState();
|
||||||
this._checkIfAlone(this.state.room);
|
this._checkIfAlone(this.state.room);
|
||||||
|
this._updateE2EStatus(this.state.room);
|
||||||
}, 500),
|
}, 500),
|
||||||
|
|
||||||
_checkIfAlone: function(room) {
|
_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 the user is a ROU, allow them to transition to a PWLU
|
||||||
if (cli && cli.isGuest()) {
|
if (cli && cli.isGuest()) {
|
||||||
// Join this room once the user has registered and logged in
|
// Join this room once the user has registered and logged in
|
||||||
const signUrl = this.props.thirdPartyInvite ?
|
// (If we failed to peek, we may not have a valid room object.)
|
||||||
this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'do_after_sync_prepared',
|
action: 'do_after_sync_prepared',
|
||||||
deferred_action: {
|
deferred_action: {
|
||||||
action: 'view_room',
|
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 { KeyCode } from '../../Keyboard';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import rate_limited_func from '../../ratelimitedfunc';
|
import { debounce } from 'lodash';
|
||||||
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
|
@ -67,12 +67,9 @@ module.exports = React.createClass({
|
||||||
this.onSearch();
|
this.onSearch();
|
||||||
},
|
},
|
||||||
|
|
||||||
onSearch: new rate_limited_func(
|
onSearch: debounce(function() {
|
||||||
function() {
|
this.props.onSearch(this.refs.search.value);
|
||||||
this.props.onSearch(this.refs.search.value);
|
}, 200, {trailing: true}),
|
||||||
},
|
|
||||||
500,
|
|
||||||
),
|
|
||||||
|
|
||||||
_onKeyDown: function(ev) {
|
_onKeyDown: function(ev) {
|
||||||
switch (ev.keyCode) {
|
switch (ev.keyCode) {
|
||||||
|
|
|
@ -90,6 +90,11 @@ module.exports = React.createClass({
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
e2eInfoClicked: function() {
|
||||||
|
this.props.e2eInfoCallback();
|
||||||
|
this.closeMenu();
|
||||||
|
},
|
||||||
|
|
||||||
onViewSourceClick: function() {
|
onViewSourceClick: function() {
|
||||||
const ViewSource = sdk.getComponent('structures.ViewSource');
|
const ViewSource = sdk.getComponent('structures.ViewSource');
|
||||||
Modal.createTrackedDialog('View Event Source', '', 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 (
|
return (
|
||||||
<div className="mx_MessageContextMenu">
|
<div className="mx_MessageContextMenu">
|
||||||
{ resendButton }
|
{ resendButton }
|
||||||
|
@ -347,6 +359,7 @@ module.exports = React.createClass({
|
||||||
{ replyButton }
|
{ replyButton }
|
||||||
{ externalURLButton }
|
{ externalURLButton }
|
||||||
{ collapseReplyThread }
|
{ collapseReplyThread }
|
||||||
|
{ e2eInfo }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -68,7 +68,7 @@ export default class TagTileContextMenu extends React.Component {
|
||||||
<hr className="mx_TagTileContextMenu_separator" />
|
<hr className="mx_TagTileContextMenu_separator" />
|
||||||
<div className="mx_TagTileContextMenu_item" onClick={this._onRemoveClick} >
|
<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" />
|
<img className="mx_TagTileContextMenu_item_icon" src={require("../../../../res/img/icon_context_delete.svg")} width="15" height="15" />
|
||||||
{ _t('Remove') }
|
{ _t('Hide') }
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,11 @@ export default class DeviceVerifyDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSwitchToLegacyClick = () => {
|
_onSwitchToLegacyClick = () => {
|
||||||
|
if (this._verifier) {
|
||||||
|
this._verifier.removeListener('show_sas', this._onVerifierShowSas);
|
||||||
|
this._verifier.cancel('User cancel');
|
||||||
|
this._verifier = null;
|
||||||
|
}
|
||||||
this.setState({mode: MODE_LEGACY});
|
this.setState({mode: MODE_LEGACY});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,11 +189,21 @@ export default class DeviceVerifyDialog extends React.Component {
|
||||||
|
|
||||||
_renderSasVerificationPhaseWaitAccept() {
|
_renderSasVerificationPhaseWaitAccept() {
|
||||||
const Spinner = sdk.getComponent("views.elements.Spinner");
|
const Spinner = sdk.getComponent("views.elements.Spinner");
|
||||||
|
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
<p>{_t("Waiting for partner to accept...")}</p>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
@ -37,9 +38,12 @@ export default class IncomingSasDialog extends React.Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
phase: PHASE_START,
|
phase: PHASE_START,
|
||||||
sasVerified: false,
|
sasVerified: false,
|
||||||
|
opponentProfile: null,
|
||||||
|
opponentProfileError: null,
|
||||||
};
|
};
|
||||||
this.props.verifier.on('show_sas', this._onVerifierShowSas);
|
this.props.verifier.on('show_sas', this._onVerifierShowSas);
|
||||||
this.props.verifier.on('cancel', this._onVerifierCancel);
|
this.props.verifier.on('cancel', this._onVerifierCancel);
|
||||||
|
this._fetchOpponentProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -49,6 +53,21 @@ export default class IncomingSasDialog extends React.Component {
|
||||||
this.props.verifier.removeListener('show_sas', this._onVerifierShowSas);
|
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 = () => {
|
_onFinished = () => {
|
||||||
this.props.onFinished(this.state.phase === PHASE_VERIFIED);
|
this.props.onFinished(this.state.phase === PHASE_VERIFIED);
|
||||||
}
|
}
|
||||||
|
@ -93,10 +112,39 @@ export default class IncomingSasDialog extends React.Component {
|
||||||
|
|
||||||
_renderPhaseStart() {
|
_renderPhaseStart() {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>{this.props.verifier.userId}</h2>
|
{profile}
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Verify this user to mark them as trusted. " +
|
"Verify this user to mark them as trusted. " +
|
||||||
"Trusting users gives you extra peace of mind when using " +
|
"Trusting users gives you extra peace of mind when using " +
|
||||||
|
|
|
@ -22,6 +22,10 @@ import { _t } from '../../../languageHandler';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
|
||||||
export default class LogoutDialog extends React.Component {
|
export default class LogoutDialog extends React.Component {
|
||||||
|
defaultProps = {
|
||||||
|
onFinished: function() {},
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._onSettingsLinkClick = this._onSettingsLinkClick.bind(this);
|
this._onSettingsLinkClick = this._onSettingsLinkClick.bind(this);
|
||||||
|
@ -29,13 +33,37 @@ export default class LogoutDialog extends React.Component {
|
||||||
this._onFinished = this._onFinished.bind(this);
|
this._onFinished = this._onFinished.bind(this);
|
||||||
this._onSetRecoveryMethodClick = this._onSetRecoveryMethodClick.bind(this);
|
this._onSetRecoveryMethodClick = this._onSetRecoveryMethodClick.bind(this);
|
||||||
this._onLogoutConfirm = this._onLogoutConfirm.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() {
|
_onSettingsLinkClick() {
|
||||||
// close dialog
|
// close dialog
|
||||||
if (this.props.onFinished) {
|
this.props.onFinished();
|
||||||
this.props.onFinished();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onExportE2eKeysClicked() {
|
_onExportE2eKeysClicked() {
|
||||||
|
@ -52,9 +80,7 @@ export default class LogoutDialog extends React.Component {
|
||||||
dis.dispatch({action: 'logout'});
|
dis.dispatch({action: 'logout'});
|
||||||
}
|
}
|
||||||
// close dialog
|
// close dialog
|
||||||
if (this.props.onFinished) {
|
this.props.onFinished();
|
||||||
this.props.onFinished();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSetRecoveryMethodClick() {
|
_onSetRecoveryMethodClick() {
|
||||||
|
@ -63,72 +89,83 @@ export default class LogoutDialog extends React.Component {
|
||||||
);
|
);
|
||||||
|
|
||||||
// close dialog
|
// close dialog
|
||||||
if (this.props.onFinished) {
|
this.props.onFinished();
|
||||||
this.props.onFinished();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onLogoutConfirm() {
|
_onLogoutConfirm() {
|
||||||
dis.dispatch({action: 'logout'});
|
dis.dispatch({action: 'logout'});
|
||||||
|
|
||||||
// close dialog
|
// close dialog
|
||||||
if (this.props.onFinished) {
|
this.props.onFinished();
|
||||||
this.props.onFinished();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const description = <div>
|
const description = <div>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"When you log out, you'll lose your secure message history. To prevent " +
|
"Encrypted messages are secured with end-to-end encryption. " +
|
||||||
"this, set up a recovery method.",
|
"Only you and the recipient(s) have the keys to read these messages.",
|
||||||
)}</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>,
|
|
||||||
},
|
|
||||||
)}</p>
|
)}</p>
|
||||||
|
<p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
|
if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
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
|
// Not quite a standard question dialog as the primary button cancels
|
||||||
// the action and does something else instead, whilst non-default button
|
// the action and does something else instead, whilst non-default button
|
||||||
// confirms the action.
|
// confirms the action.
|
||||||
return (<BaseDialog
|
return (<BaseDialog
|
||||||
title={_t("Warning!")}
|
title={_t("You'll lose access to your encrypted messages")}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
hasCancel={false}
|
hasCancel={true}
|
||||||
onFinsihed={this._onFinished}
|
onFinished={this._onFinished}
|
||||||
>
|
>
|
||||||
<div className="mx_Dialog_content" id='mx_Dialog_content'>
|
{dialogContent}
|
||||||
{ 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>
|
|
||||||
</BaseDialog>);
|
</BaseDialog>);
|
||||||
} else {
|
} else {
|
||||||
const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
|
const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
|
||||||
return (<QuestionDialog
|
return (<QuestionDialog
|
||||||
hasCancelButton={true}
|
hasCancelButton={true}
|
||||||
title={_t("Sign out")}
|
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(
|
description={_t(
|
||||||
"When signing in again, you can access encrypted chat history by " +
|
"Are you sure you want to sign out?",
|
||||||
"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).",
|
|
||||||
)}
|
)}
|
||||||
button={_t("Sign out")}
|
button={_t("Sign out")}
|
||||||
onFinished={this._onFinished}
|
onFinished={this._onFinished}
|
||||||
|
|
|
@ -25,35 +25,12 @@ import { _t } from '../../../languageHandler';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { markAllDevicesKnown } from '../../../cryptodevices';
|
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) {
|
function UserUnknownDeviceList(props) {
|
||||||
|
const MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo');
|
||||||
const {userId, userDevices} = props;
|
const {userId, userDevices} = props;
|
||||||
|
|
||||||
const deviceListEntries = Object.keys(userDevices).map((deviceId) =>
|
const deviceListEntries = Object.keys(userDevices).map((deviceId) =>
|
||||||
<DeviceListEntry key={deviceId} userId={userId}
|
<li key={deviceId}><MemberDeviceInfo device={userDevices[deviceId]} userId={userId} showDeviceId={true} /></li>,
|
||||||
device={userDevices[deviceId]} />,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -230,10 +230,15 @@ export default React.createClass({
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
title = _t("Enter Recovery Passphrase");
|
title = _t("Enter Recovery Passphrase");
|
||||||
content = <div>
|
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 " +
|
"Access your secure message history and set up secure " +
|
||||||
"messaging by entering your recovery passphrase.",
|
"messaging by entering your recovery passphrase.",
|
||||||
)}<br />
|
)}</p>
|
||||||
|
|
||||||
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
|
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
|
||||||
<input type="password"
|
<input type="password"
|
||||||
|
@ -288,10 +293,15 @@ export default React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
content = <div>
|
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 " +
|
"Access your secure message history and set up secure " +
|
||||||
"messaging by entering your recovery key.",
|
"messaging by entering your recovery key.",
|
||||||
)}<br />
|
)}</p>
|
||||||
|
|
||||||
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
|
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
|
||||||
<input className="mx_RestoreKeyBackupDialog_recoveryKeyInput"
|
<input className="mx_RestoreKeyBackupDialog_recoveryKeyInput"
|
||||||
|
|
|
@ -579,8 +579,8 @@ export default class AppTile extends React.Component {
|
||||||
// editing is done in scalar
|
// editing is done in scalar
|
||||||
const canUserModify = this._canUserModify();
|
const canUserModify = this._canUserModify();
|
||||||
const showEditButton = Boolean(this._scalarClient && canUserModify);
|
const showEditButton = Boolean(this._scalarClient && canUserModify);
|
||||||
const showDeleteButton = canUserModify;
|
const showDeleteButton = (this.props.showDelete === undefined || this.props.showDelete) && canUserModify;
|
||||||
const showCancelButton = !showDeleteButton;
|
const showCancelButton = (this.props.showCancel === undefined || this.props.showCancel) && !showDeleteButton;
|
||||||
// Picture snapshot - only show button when apps are maximised.
|
// Picture snapshot - only show button when apps are maximised.
|
||||||
const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show;
|
const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show;
|
||||||
const showMinimiseButton = this.props.showMinimise && this.props.show;
|
const showMinimiseButton = this.props.showMinimise && this.props.show;
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
|
||||||
export default function(props) {
|
export default function(props) {
|
||||||
const isWarning = props.status === "warning";
|
const isWarning = props.status === "warning";
|
||||||
|
@ -35,5 +36,10 @@ export default function(props) {
|
||||||
_t("All devices for this user are trusted") :
|
_t("All devices for this user are trusted") :
|
||||||
_t("All devices in this encrypted room 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,
|
top: y,
|
||||||
eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined,
|
eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined,
|
||||||
collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined,
|
collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined,
|
||||||
|
e2eInfoCallback: () => this.onCryptoClicked(),
|
||||||
onFinished: function() {
|
onFinished: function() {
|
||||||
self.setState({menu: false});
|
self.setState({menu: false});
|
||||||
},
|
},
|
||||||
|
@ -773,29 +774,31 @@ module.exports.haveTileForEvent = function(e) {
|
||||||
|
|
||||||
function E2ePadlockUndecryptable(props) {
|
function E2ePadlockUndecryptable(props) {
|
||||||
return (
|
return (
|
||||||
<E2ePadlock title={_t("Undecryptable")} icon="undecryptable" />
|
<E2ePadlock title={_t("Undecryptable")} icon="undecryptable" {...props} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function E2ePadlockUnverified(props) {
|
function E2ePadlockUnverified(props) {
|
||||||
return (
|
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) {
|
function E2ePadlockUnencrypted(props) {
|
||||||
return (
|
return (
|
||||||
<E2ePadlock title={_t("Unencrypted message")} icon="unencrypted" />
|
<E2ePadlock title={_t("Unencrypted message")} icon="unencrypted" {...props} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function E2ePadlock(props) {
|
function E2ePadlock(props) {
|
||||||
if (SettingsStore.getValue("alwaysShowEncryptionIcons")) {
|
if (SettingsStore.getValue("alwaysShowEncryptionIcons")) {
|
||||||
return <div
|
return (<div
|
||||||
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${props.icon}`}
|
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${props.icon}`}
|
||||||
title={props.title} onClick={props.onClick} />;
|
title={props.title} onClick={props.onClick} />);
|
||||||
} else {
|
} 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(),
|
mx_MemberDeviceInfo_icon_unverified: this.props.device.isUnverified(),
|
||||||
});
|
});
|
||||||
const indicator = (<div className={iconClasses} />);
|
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() ? this.props.device.getDisplayName() : "") + " (" + this.props.device.deviceId + ")" :
|
||||||
this.props.device.getDisplayName();
|
this.props.device.getDisplayName();
|
||||||
|
|
||||||
|
|
|
@ -941,6 +941,8 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
let roomMemberDetails = null;
|
let roomMemberDetails = null;
|
||||||
|
let e2eIconElement;
|
||||||
|
|
||||||
if (this.props.member.roomId) { // is in room
|
if (this.props.member.roomId) { // is in room
|
||||||
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
||||||
roomMemberDetails = <div>
|
roomMemberDetails = <div>
|
||||||
|
@ -959,6 +961,11 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
{statusLabel}
|
{statusLabel}
|
||||||
</div>
|
</div>
|
||||||
</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();
|
const avatarUrl = this.props.member.getMxcAvatarUrl();
|
||||||
|
@ -967,7 +974,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
const httpUrl = this.props.matrixClient.mxcUrlToHttp(avatarUrl, 800, 800);
|
const httpUrl = this.props.matrixClient.mxcUrlToHttp(avatarUrl, 800, 800);
|
||||||
avatarElement = <div className="mx_MemberInfo_avatar">
|
avatarElement = <div className="mx_MemberInfo_avatar">
|
||||||
<img src={httpUrl} />
|
<img src={httpUrl} />
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
||||||
|
@ -979,7 +986,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}>
|
<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')} />
|
<img src={require("../../../../res/img/minimise.svg")} width="10" height="16" className="mx_filterFlipColor" alt={_t('Close')} />
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
{ this.state.e2eStatus ? <E2EIcon status={this.state.e2eStatus} isUser={true} /> : undefined }
|
{ e2eIconElement }
|
||||||
<EmojiText element="h2">{ memberName }</EmojiText>
|
<EmojiText element="h2">{ memberName }</EmojiText>
|
||||||
</div>
|
</div>
|
||||||
{ avatarElement }
|
{ 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 SettingsStore from "../../../settings/SettingsStore";
|
||||||
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
||||||
import E2EIcon from './E2EIcon';
|
import E2EIcon from './E2EIcon';
|
||||||
|
import * as cryptodevices from '../../../cryptodevices';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomHeader',
|
displayName: 'RoomHeader',
|
||||||
|
@ -145,6 +146,12 @@ module.exports = React.createClass({
|
||||||
return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0);
|
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() {
|
render: function() {
|
||||||
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
|
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
@ -156,7 +163,7 @@ module.exports = React.createClass({
|
||||||
let pinnedEventsButton = null;
|
let pinnedEventsButton = null;
|
||||||
|
|
||||||
const e2eIcon = this.props.e2eStatus ?
|
const e2eIcon = this.props.e2eStatus ?
|
||||||
<E2EIcon status={this.props.e2eStatus} /> :
|
<E2EIcon status={this.props.e2eStatus} onClick={this._onShowDevicesClick} /> :
|
||||||
undefined;
|
undefined;
|
||||||
|
|
||||||
if (this.props.onCancelClick) {
|
if (this.props.onCancelClick) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import Timer from "../../../utils/Timer";
|
||||||
|
|
||||||
const React = require("react");
|
const React = require("react");
|
||||||
const ReactDOM = require("react-dom");
|
const ReactDOM = require("react-dom");
|
||||||
|
@ -41,6 +42,7 @@ import {Resizer} from '../../../resizer';
|
||||||
import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2';
|
import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2';
|
||||||
const HIDE_CONFERENCE_CHANS = true;
|
const HIDE_CONFERENCE_CHANS = true;
|
||||||
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
|
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
|
||||||
|
const HOVER_MOVE_TIMEOUT = 1000;
|
||||||
|
|
||||||
function labelForTagName(tagName) {
|
function labelForTagName(tagName) {
|
||||||
if (tagName.startsWith('u.')) return tagName.slice(2);
|
if (tagName.startsWith('u.')) return tagName.slice(2);
|
||||||
|
@ -73,6 +75,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
|
||||||
|
this._hoverClearTimer = null;
|
||||||
this._subListRefs = {
|
this._subListRefs = {
|
||||||
// key => RoomSubList ref
|
// key => RoomSubList ref
|
||||||
};
|
};
|
||||||
|
@ -95,7 +98,7 @@ module.exports = React.createClass({
|
||||||
// update overflow indicators
|
// update overflow indicators
|
||||||
this._checkSubListsOverflow();
|
this._checkSubListsOverflow();
|
||||||
// don't store height for collapsed sublists
|
// don't store height for collapsed sublists
|
||||||
if(!this.collapsedState[key]) {
|
if (!this.collapsedState[key]) {
|
||||||
this.subListSizes[key] = size;
|
this.subListSizes[key] = size;
|
||||||
window.localStorage.setItem("mx_roomlist_sizes",
|
window.localStorage.setItem("mx_roomlist_sizes",
|
||||||
JSON.stringify(this.subListSizes));
|
JSON.stringify(this.subListSizes));
|
||||||
|
@ -357,11 +360,32 @@ module.exports = React.createClass({
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseEnter: function(ev) {
|
onMouseMove: async function(ev) {
|
||||||
this.setState({hover: true});
|
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) {
|
onMouseLeave: function(ev) {
|
||||||
|
if (this._hoverClearTimer) {
|
||||||
|
this._hoverClearTimer.abort();
|
||||||
|
this._hoverClearTimer = null;
|
||||||
|
}
|
||||||
this.setState({hover: false});
|
this.setState({hover: false});
|
||||||
|
|
||||||
// Refresh the room list just in case the user missed something.
|
// Refresh the room list just in case the user missed something.
|
||||||
|
@ -774,7 +798,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={this._collectResizeContainer} className="mx_RoomList"
|
<div ref={this._collectResizeContainer} className="mx_RoomList"
|
||||||
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
onMouseMove={this.onMouseMove} onMouseLeave={this.onMouseLeave}>
|
||||||
{ subListComponents }
|
{ subListComponents }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -39,6 +39,7 @@ export default class RoomRecoveryReminder extends React.PureComponent {
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null,
|
error: null,
|
||||||
backupInfo: null,
|
backupInfo: null,
|
||||||
|
notNowClicked: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +78,10 @@ export default class RoomRecoveryReminder extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onOnNotNowClick = () => {
|
||||||
|
this.setState({notNowClicked: true});
|
||||||
|
}
|
||||||
|
|
||||||
onDontAskAgainClick = () => {
|
onDontAskAgainClick = () => {
|
||||||
// When you choose "Don't ask again" from the room reminder, we show a
|
// When you choose "Don't ask again" from the room reminder, we show a
|
||||||
// dialog to confirm the choice.
|
// dialog to confirm the choice.
|
||||||
|
@ -104,46 +109,54 @@ export default class RoomRecoveryReminder extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
||||||
|
|
||||||
let body;
|
let setupCaption;
|
||||||
if (this.state.error) {
|
if (this.state.backupInfo) {
|
||||||
body = <div className="error">
|
setupCaption = _t("Use Key Backup");
|
||||||
{_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>;
|
|
||||||
} else {
|
} else {
|
||||||
body = _t(
|
setupCaption = _t("Start using Key Backup");
|
||||||
"Securely back up your decryption keys to the server to make sure " +
|
|
||||||
"you'll always be able to read your encrypted messages.",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomRecoveryReminder">
|
<div className="mx_RoomRecoveryReminder">
|
||||||
<div className="mx_RoomRecoveryReminder_header">{_t(
|
<div className="mx_RoomRecoveryReminder_header">{_t(
|
||||||
"Don't risk losing your encrypted messages!",
|
"Never lose encrypted messages",
|
||||||
)}</div>
|
)}</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">
|
<div className="mx_RoomRecoveryReminder_buttons">
|
||||||
<AccessibleButton className="mx_RoomRecoveryReminder_button"
|
<AccessibleButton className="mx_RoomRecoveryReminder_button"
|
||||||
onClick={this.onSetupClick}>
|
onClick={this.onSetupClick}>
|
||||||
{_t("Activate Secure Key Backup")}
|
{setupCaption}
|
||||||
</AccessibleButton>
|
</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"
|
<p><AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
|
||||||
onClick={this.onDontAskAgainClick}>
|
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>
|
</AccessibleButton></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -108,13 +108,6 @@ module.exports = React.createClass({
|
||||||
return statusUser._unstable_statusMessage;
|
return statusUser._unstable_statusMessage;
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomTimeline: function(ev, room) {
|
|
||||||
if (room !== this.props.room) return;
|
|
||||||
this.setState({
|
|
||||||
notificationCount: this.props.room.getUnreadNotificationCount(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onRoomName: function(room) {
|
onRoomName: function(room) {
|
||||||
if (room !== this.props.room) return;
|
if (room !== this.props.room) return;
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -159,7 +152,6 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
|
||||||
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
||||||
ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
|
ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
@ -179,7 +171,6 @@ module.exports = React.createClass({
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli) {
|
if (cli) {
|
||||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
|
||||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||||
}
|
}
|
||||||
ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
|
ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||||
|
@ -306,7 +297,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const isInvite = this.props.room.getMyMembership() === "invite";
|
const isInvite = this.props.room.getMyMembership() === "invite";
|
||||||
const notificationCount = this.state.notificationCount;
|
const notificationCount = this.props.notificationCount;
|
||||||
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
|
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
|
||||||
|
|
||||||
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();
|
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();
|
||||||
|
|
|
@ -226,6 +226,7 @@ export default class Stickerpicker extends React.Component {
|
||||||
showTitle={false}
|
showTitle={false}
|
||||||
showMinimise={true}
|
showMinimise={true}
|
||||||
showDelete={false}
|
showDelete={false}
|
||||||
|
showCancel={false}
|
||||||
showPopout={false}
|
showPopout={false}
|
||||||
onMinimiseClick={this._onHideStickersClick}
|
onMinimiseClick={this._onHideStickersClick}
|
||||||
handleMinimisePointerEvents={true}
|
handleMinimisePointerEvents={true}
|
||||||
|
|
|
@ -111,10 +111,10 @@ export default class KeyBackupPanel extends React.PureComponent {
|
||||||
Modal.createTrackedDialog('Delete Backup', '', QuestionDialog, {
|
Modal.createTrackedDialog('Delete Backup', '', QuestionDialog, {
|
||||||
title: _t('Delete Backup'),
|
title: _t('Delete Backup'),
|
||||||
description: _t(
|
description: _t(
|
||||||
"Delete your backed up encryption keys from the server? " +
|
"Are you sure? You will lose your encrypted messages if your " +
|
||||||
"You will no longer be able to use your recovery key to read encrypted message history",
|
"keys are not backed up properly.",
|
||||||
),
|
),
|
||||||
button: _t('Delete backup'),
|
button: _t('Delete Backup'),
|
||||||
danger: true,
|
danger: true,
|
||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
@ -135,6 +135,10 @@ export default class KeyBackupPanel extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
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) {
|
if (this.state.error) {
|
||||||
return (
|
return (
|
||||||
|
@ -145,14 +149,25 @@ export default class KeyBackupPanel extends React.PureComponent {
|
||||||
} else if (this.state.loading) {
|
} else if (this.state.loading) {
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
} else if (this.state.backupInfo) {
|
} else if (this.state.backupInfo) {
|
||||||
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
let clientBackupStatus;
|
let clientBackupStatus;
|
||||||
|
let restoreButtonCaption = _t("Restore from Backup");
|
||||||
|
|
||||||
if (MatrixClientPeg.get().getKeyBackupEnabled()) {
|
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 {
|
} else {
|
||||||
clientBackupStatus = _t(
|
clientBackupStatus = <div>
|
||||||
"This device is <b>not</b> using key backup. Restore the backup to start using it.", {},
|
<p>{encryptedMessageAreEncrypted}</p>
|
||||||
{b: x => <b>{x}</b>},
|
<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;
|
let uploadStatus;
|
||||||
|
@ -243,18 +258,25 @@ export default class KeyBackupPanel extends React.PureComponent {
|
||||||
</details>
|
</details>
|
||||||
<p>
|
<p>
|
||||||
<AccessibleButton kind="primary" onClick={this._restoreBackup}>
|
<AccessibleButton kind="primary" onClick={this._restoreBackup}>
|
||||||
{ _t("Restore backup") }
|
{restoreButtonCaption}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<AccessibleButton kind="danger" onClick={this._deleteBackup}>
|
<AccessibleButton kind="danger" onClick={this._deleteBackup}>
|
||||||
{ _t("Delete backup") }
|
{ _t("Delete Backup") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</p>
|
</p>
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else {
|
||||||
return <div>
|
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}>
|
<AccessibleButton kind="primary" onClick={this._startNewBackup}>
|
||||||
{ _t("Start a new backup") }
|
{ _t("Start using Key Backup") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -270,6 +270,7 @@
|
||||||
"Failed to join room": "Failed to join room",
|
"Failed to join room": "Failed to join room",
|
||||||
"Message Pinning": "Message Pinning",
|
"Message Pinning": "Message Pinning",
|
||||||
"Custom user status messages": "Custom user status messages",
|
"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)",
|
"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",
|
"Render simple counters in room header": "Render simple counters in room header",
|
||||||
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||||
|
@ -439,11 +440,14 @@
|
||||||
"Disable Notifications": "Disable Notifications",
|
"Disable Notifications": "Disable Notifications",
|
||||||
"Enable Notifications": "Enable Notifications",
|
"Enable Notifications": "Enable Notifications",
|
||||||
"Delete Backup": "Delete Backup",
|
"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",
|
"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.",
|
||||||
"Delete backup": "Delete backup",
|
"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",
|
"Unable to load key backup status": "Unable to load key backup status",
|
||||||
"This device is using key backup": "This device is using key backup",
|
"Restore from Backup": "Restore from 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.",
|
"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...",
|
"Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...",
|
||||||
"All keys backed up": "All keys backed up",
|
"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.",
|
"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",
|
"Advanced": "Advanced",
|
||||||
"Backup version: ": "Backup version: ",
|
"Backup version: ": "Backup version: ",
|
||||||
"Algorithm: ": "Algorithm: ",
|
"Algorithm: ": "Algorithm: ",
|
||||||
"Restore backup": "Restore 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>.",
|
||||||
"No backup is present": "No backup is present",
|
"Start using Key Backup": "Start using Key Backup",
|
||||||
"Start a new backup": "Start a new backup",
|
|
||||||
"Error saving email notification preferences": "Error saving email notification preferences",
|
"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.",
|
"An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.",
|
||||||
"Keywords": "Keywords",
|
"Keywords": "Keywords",
|
||||||
|
@ -785,11 +788,12 @@
|
||||||
"You are trying to access a room.": "You are trying to access a room.",
|
"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!",
|
"<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",
|
"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.",
|
"Use Key Backup": "Use Key Backup",
|
||||||
"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.",
|
"Never lose encrypted messages": "Never lose encrypted messages",
|
||||||
"Don't risk losing your encrypted messages!": "Don't risk losing your 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.",
|
||||||
"Activate Secure Key Backup": "Activate Secure Key Backup",
|
"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>",
|
||||||
"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",
|
"Not now": "Not now",
|
||||||
|
"Don't ask me again": "Don't ask me again",
|
||||||
"Add a topic": "Add a topic",
|
"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.",
|
"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.",
|
"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.",
|
"Verify by comparing a short text string.": "Verify by comparing a short text string.",
|
||||||
"Begin Verifying": "Begin Verifying",
|
"Begin Verifying": "Begin Verifying",
|
||||||
"Waiting for partner to accept...": "Waiting for partner to accept...",
|
"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...",
|
"Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...",
|
||||||
"Use two-way text verification": "Use two-way text verification",
|
"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:",
|
"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",
|
"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!",
|
"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",
|
"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.",
|
"I don't want my encrypted messages": "I don't want my encrypted messages",
|
||||||
"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.",
|
"Manually export keys": "Manually export keys",
|
||||||
"Set a Recovery Method": "Set a Recovery Method",
|
"You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages",
|
||||||
"I understand, log out without": "I understand, log out without",
|
"Are you sure you want to sign out?": "Are you sure you want to sign out?",
|
||||||
"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).",
|
|
||||||
"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.",
|
"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.",
|
"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",
|
"Report bugs & give feedback": "Report bugs & give feedback",
|
||||||
|
@ -1171,6 +1175,7 @@
|
||||||
"Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!",
|
"Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!",
|
||||||
"Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys",
|
"Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys",
|
||||||
"Enter Recovery Passphrase": "Enter Recovery Passphrase",
|
"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.",
|
"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",
|
"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>",
|
"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 status": "Set status",
|
||||||
"Set a new status...": "Set a new status...",
|
"Set a new status...": "Set a new status...",
|
||||||
"View Community": "View Community",
|
"View Community": "View Community",
|
||||||
|
"Hide": "Hide",
|
||||||
"Login": "Login",
|
"Login": "Login",
|
||||||
"powered by Matrix": "powered by Matrix",
|
"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>",
|
"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",
|
"Import": "Import",
|
||||||
"Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.",
|
"Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.",
|
||||||
"Keep going...": "Keep going...",
|
"Keep going...": "Keep going...",
|
||||||
"Secure your encrypted message history with a Recovery Passphrase.": "Secure your encrypted message history with a Recovery Passphrase.",
|
"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.",
|
||||||
"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.",
|
"For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.",
|
||||||
"Enter a passphrase...": "Enter a passphrase...",
|
"Enter a 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>.",
|
"Set up with a Recovery Key": "Set up with a Recovery Key",
|
||||||
"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>.",
|
|
||||||
"That matches!": "That matches!",
|
"That matches!": "That matches!",
|
||||||
"That doesn't match.": "That doesn't match.",
|
"That doesn't match.": "That doesn't match.",
|
||||||
"Go back to set it again.": "Go back to set it again.",
|
"Go back to set it again.": "Go back to set it again.",
|
||||||
"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...",
|
"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 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.",
|
"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",
|
"Your Recovery Key": "Your Recovery Key",
|
||||||
"Copy to clipboard": "Copy to clipboard",
|
"Copy to clipboard": "Copy to clipboard",
|
||||||
"Download": "Download",
|
"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>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>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",
|
"<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.",
|
"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",
|
"Set up Secure Message Recovery": "Set up Secure Message Recovery",
|
||||||
"Create a Recovery Passphrase": "Create a Recovery Passphrase",
|
"Secure your backup with a passphrase": "Secure your backup with a passphrase",
|
||||||
"Confirm Recovery Passphrase": "Confirm Recovery Passphrase",
|
"Confirm your passphrase": "Confirm your passphrase",
|
||||||
"Recovery Key": "Recovery Key",
|
"Recovery key": "Recovery key",
|
||||||
"Keep it safe": "Keep it safe",
|
"Keep it safe": "Keep it safe",
|
||||||
"Starting backup...": "Starting backup...",
|
"Starting backup...": "Starting backup...",
|
||||||
"Backup Started": "Backup Started",
|
"Success!": "Success!",
|
||||||
"Create Key Backup": "Create Key Backup",
|
"Create Key Backup": "Create Key Backup",
|
||||||
"Unable to create key backup": "Unable to create key backup",
|
"Unable to create key backup": "Unable to create key backup",
|
||||||
"Retry": "Retry",
|
"Retry": "Retry",
|
||||||
|
|
|
@ -20,54 +20,28 @@ limitations under the License.
|
||||||
* to update the interface once for all of them.
|
* to update the interface once for all of them.
|
||||||
*
|
*
|
||||||
* Note that the function must not take arguments, since the args
|
* 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
|
* The returned function has a 'cancelPendingCall' property which can be called
|
||||||
* on unmount or similar to cancel any pending update.
|
* on unmount or similar to cancel any pending update.
|
||||||
*/
|
*/
|
||||||
module.exports = function(f, minIntervalMs) {
|
|
||||||
this.lastCall = 0;
|
|
||||||
this.scheduledCall = undefined;
|
|
||||||
|
|
||||||
const self = this;
|
import { throttle } from "lodash";
|
||||||
const wrapper = function() {
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
if (self.lastCall < now - minIntervalMs) {
|
export default function ratelimitedfunc(fn, time) {
|
||||||
f.apply(this);
|
const throttledFn = throttle(fn, time, {
|
||||||
// get the time again now the function has finished, so if it
|
leading: true,
|
||||||
// took longer than the delay time to execute, it doesn't
|
trailing: true,
|
||||||
// immediately become eligible to run again.
|
});
|
||||||
self.lastCall = Date.now();
|
const _bind = throttledFn.bind;
|
||||||
} else if (self.scheduledCall === undefined) {
|
throttledFn.bind = function() {
|
||||||
self.scheduledCall = setTimeout(
|
const boundFn = _bind.apply(throttledFn, arguments);
|
||||||
() => {
|
boundFn.cancelPendingCall = throttledFn.cancelPendingCall;
|
||||||
self.scheduledCall = undefined;
|
return boundFn;
|
||||||
f.apply(this);
|
|
||||||
// get time again as per above
|
|
||||||
self.lastCall = Date.now();
|
|
||||||
},
|
|
||||||
(self.lastCall + minIntervalMs) - now,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// add the cancelPendingCall property
|
throttledFn.cancelPendingCall = function() {
|
||||||
wrapper.cancelPendingCall = function() {
|
throttledFn.cancel();
|
||||||
if (self.scheduledCall) {
|
|
||||||
clearTimeout(self.scheduledCall);
|
|
||||||
self.scheduledCall = undefined;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
return throttledFn;
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
|
|
|
@ -99,6 +99,12 @@ export const SETTINGS = {
|
||||||
default: false,
|
default: false,
|
||||||
controller: new CustomStatusController(),
|
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": {
|
"feature_custom_tags": {
|
||||||
isFeature: true,
|
isFeature: true,
|
||||||
displayName: _td("Group & filter rooms by custom tags (refresh to apply changes)"),
|
displayName: _td("Group & filter rooms by custom tags (refresh to apply changes)"),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue