Merge pull request #2557 from matrix-org/bwindels/e2eicons
Add e2e icon to room header/composer/member info, more ...
This commit is contained in:
commit
45e982ac13
23 changed files with 251 additions and 94 deletions
|
@ -107,6 +107,7 @@
|
||||||
@import "./views/rooms/_AppsDrawer.scss";
|
@import "./views/rooms/_AppsDrawer.scss";
|
||||||
@import "./views/rooms/_Autocomplete.scss";
|
@import "./views/rooms/_Autocomplete.scss";
|
||||||
@import "./views/rooms/_AuxPanel.scss";
|
@import "./views/rooms/_AuxPanel.scss";
|
||||||
|
@import "./views/rooms/_E2EIcon.scss";
|
||||||
@import "./views/rooms/_EntityTile.scss";
|
@import "./views/rooms/_EntityTile.scss";
|
||||||
@import "./views/rooms/_EventTile.scss";
|
@import "./views/rooms/_EventTile.scss";
|
||||||
@import "./views/rooms/_JumpToBottomButton.scss";
|
@import "./views/rooms/_JumpToBottomButton.scss";
|
||||||
|
|
|
@ -121,7 +121,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomStatusBar_connectionLostBar img {
|
.mx_RoomStatusBar_connectionLostBar img {
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 22px;
|
padding-right: 10px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
33
res/css/views/rooms/_E2EIcon.scss
Normal file
33
res/css/views/rooms/_E2EIcon.scss
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
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_E2EIcon {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center 0;
|
||||||
|
margin: 0 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_E2EIcon_verified {
|
||||||
|
mask-image: url('$(res)/img/feather-icons/e2e/lock-verified.svg');
|
||||||
|
background-color: $accent-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_E2EIcon_warning {
|
||||||
|
mask-image: url('$(res)/img/feather-icons/e2e/lock-warning.svg');
|
||||||
|
background-color: $warning-color;
|
||||||
|
}
|
|
@ -281,9 +281,24 @@ limitations under the License.
|
||||||
.mx_EventTile_e2eIcon {
|
.mx_EventTile_e2eIcon {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 9px;
|
top: 8px;
|
||||||
left: 46px;
|
left: 46px;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
mask-size: 14px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_e2eIcon_undecryptable, .mx_EventTile_e2eIcon_unverified {
|
||||||
|
mask-image: url('$(res)/img/feather-icons/e2e/warning.svg');
|
||||||
|
background-color: $warning-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_e2eIcon_unencrypted {
|
||||||
|
mask-image: url('$(res)/img/feather-icons/e2e/warning.svg');
|
||||||
|
background-color: $composer-e2e-icon-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_e2eIcon_hidden {
|
.mx_EventTile_e2eIcon_hidden {
|
||||||
|
|
|
@ -20,6 +20,25 @@ limitations under the License.
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MemberDeviceInfo_icon {
|
||||||
|
margin-top: 4px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
.mx_MemberDeviceInfo_icon_blacklisted {
|
||||||
|
mask-image: url('$(res)/img/feather-icons/e2e/blacklisted.svg');
|
||||||
|
background-color: $warning-color;
|
||||||
|
}
|
||||||
|
.mx_MemberDeviceInfo_icon_verified {
|
||||||
|
mask-image: url('$(res)/img/feather-icons/e2e/verified.svg');
|
||||||
|
background-color: $accent-color;
|
||||||
|
}
|
||||||
|
.mx_MemberDeviceInfo_icon_unverified {
|
||||||
|
mask-image: url('$(res)/img/feather-icons/e2e/warning.svg');
|
||||||
|
background-color: $warning-color;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MemberDeviceInfo > .mx_DeviceVerifyButtons {
|
.mx_MemberDeviceInfo > .mx_DeviceVerifyButtons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -26,6 +26,10 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MemberInfo_name > .mx_E2EIcon {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MemberInfo_cancel {
|
.mx_MemberInfo_cancel {
|
||||||
height: 16px;
|
height: 16px;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
|
|
|
@ -23,6 +23,10 @@ limitations under the License.
|
||||||
padding-left: 84px;
|
padding-left: 84px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MessageComposer_wrapper.mx_MessageComposer_hasE2EIcon {
|
||||||
|
padding-left: 109px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MessageComposer_replaced_wrapper {
|
.mx_MessageComposer_replaced_wrapper {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
@ -71,9 +75,10 @@ limitations under the License.
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageComposer_e2eIcon {
|
.mx_MessageComposer_e2eIcon.mx_E2EIcon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 60px;
|
left: 60px;
|
||||||
|
background-color: $composer-e2e-icon-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageComposer_noperm_error {
|
.mx_MessageComposer_noperm_error {
|
||||||
|
|
6
res/img/feather-icons/e2e/blacklisted.svg
Normal file
6
res/img/feather-icons/e2e/blacklisted.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12" viewBox="0 0 12 12">
|
||||||
|
<defs>
|
||||||
|
<path id="a" d="M5 10A5 5 0 1 0 5 0a5 5 0 0 0 0 10zM2.5 3.5h5a1.5 1.5 0 0 1 0 3h-5a1.5 1.5 0 0 1 0-3z"/>
|
||||||
|
</defs>
|
||||||
|
<use fill="#F56679" fill-rule="evenodd" stroke="#F56679" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" transform="translate(1 1)" xlink:href="#a"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 442 B |
6
res/img/feather-icons/e2e/lock-verified.svg
Normal file
6
res/img/feather-icons/e2e/lock-verified.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="25" viewBox="0 0 22 25">
|
||||||
|
<g fill="none" fill-rule="evenodd" stroke="#7AC9A1" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke-width="2" d="M8.23 21.01l-5.233-.007a1.995 1.995 0 0 1-1.997-2V11a2 2 0 0 1 2-2h14c1.259 0 2 .939 2 1M5 9V6a5 5 0 1 1 10 0v3"/>
|
||||||
|
<path fill="#7AC9A1" d="M15.5 24s5.5-2.4 5.5-6v-4.2L15.5 12 10 13.8V18c0 3.6 5.5 6 5.5 6z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 454 B |
9
res/img/feather-icons/e2e/lock-warning.svg
Normal file
9
res/img/feather-icons/e2e/lock-warning.svg
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="23" height="25" viewBox="0 0 23 25">
|
||||||
|
<defs>
|
||||||
|
<path id="a" d="M15 23a6 6 0 1 0 0-12 6 6 0 0 0 0 12zm0-10.5a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-3 0v-3a1.5 1.5 0 0 1 1.5-1.5zm0 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/>
|
||||||
|
</defs>
|
||||||
|
<g fill="none" fill-rule="evenodd" stroke="#F56679" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
|
||||||
|
<path stroke-width="2" d="M7.23 20.01l-5.233-.007a2 2 0 0 1-1.997-2V10a2 2 0 0 1 2-2h14c1.259 0 2 .939 2 1M4 8V5a5 5 0 1 1 10 0v3"/>
|
||||||
|
<use fill="#F56679" xlink:href="#a"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 665 B |
3
res/img/feather-icons/e2e/verified.svg
Normal file
3
res/img/feather-icons/e2e/verified.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="12" viewBox="0 0 11 12">
|
||||||
|
<path fill="#7AC9A1" fill-rule="evenodd" stroke="#7AC9A1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M5.5 11S10 9 10 6V2.5L5.5 1 1 2.5V6c0 3 4.5 5 4.5 5z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 278 B |
6
res/img/feather-icons/e2e/warning.svg
Normal file
6
res/img/feather-icons/e2e/warning.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12" viewBox="0 0 12 12">
|
||||||
|
<defs>
|
||||||
|
<path id="a" d="M5 10A5 5 0 1 0 5 0a5 5 0 0 0 0 10zM5 .5A1.5 1.5 0 0 1 6.5 2v3a1.5 1.5 0 0 1-3 0V2A1.5 1.5 0 0 1 5 .5zm0 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/>
|
||||||
|
</defs>
|
||||||
|
<use fill="#F56679" fill-rule="evenodd" stroke="#F56679" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" transform="translate(1 1)" xlink:href="#a"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 500 B |
|
@ -142,6 +142,8 @@ $roomheader-addroom-color: #91A1C0;
|
||||||
$roomtopic-color: #9fa9ba;
|
$roomtopic-color: #9fa9ba;
|
||||||
$eventtile-meta-color: $roomtopic-color;
|
$eventtile-meta-color: $roomtopic-color;
|
||||||
|
|
||||||
|
$composer-e2e-icon-color: #c9ced6;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
$roomtile-name-color: #61708b;
|
$roomtile-name-color: #61708b;
|
||||||
|
|
|
@ -134,6 +134,9 @@ $roomheader-color: $primary-fg-color;
|
||||||
$roomheader-addroom-color: $primary-bg-color;
|
$roomheader-addroom-color: $primary-bg-color;
|
||||||
$roomtopic-color: $settings-grey-fg-color;
|
$roomtopic-color: $settings-grey-fg-color;
|
||||||
$eventtile-meta-color: $roomtopic-color;
|
$eventtile-meta-color: $roomtopic-color;
|
||||||
|
|
||||||
|
$composer-e2e-icon-color: #c9ced6;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
$roomtile-name-color: rgba(69, 69, 69, 0.8);
|
$roomtile-name-color: rgba(69, 69, 69, 0.8);
|
||||||
|
|
|
@ -290,7 +290,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_RoomStatusBar_connectionLostBar">
|
return <div className="mx_RoomStatusBar_connectionLostBar">
|
||||||
<img src={require("../../../res/img/warning.svg")} width="24" height="23" title={_t("Warning")} alt="" />
|
<img src={require("../../../res/img/feather-icons/e2e/warning.svg")} width="24" height="24" title={_t("Warning")} alt="" />
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
||||||
{ title }
|
{ title }
|
||||||
|
@ -309,7 +309,7 @@ module.exports = React.createClass({
|
||||||
if (this._shouldShowConnectionError()) {
|
if (this._shouldShowConnectionError()) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomStatusBar_connectionLostBar">
|
<div className="mx_RoomStatusBar_connectionLostBar">
|
||||||
<img src={require("../../../res/img/warning.svg")} width="24" height="23" title="/!\ " alt="/!\ " />
|
<img src={require("../../../res/img/feather-icons/e2e/warning.svg")} width="24" height="24" title="/!\ " alt="/!\ " />
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
||||||
{ _t('Connectivity to the server has been lost.') }
|
{ _t('Connectivity to the server has been lost.') }
|
||||||
|
|
|
@ -168,6 +168,7 @@ module.exports = React.createClass({
|
||||||
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
|
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
|
||||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||||
MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
||||||
|
MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||||
this._fetchMediaConfig();
|
this._fetchMediaConfig();
|
||||||
// Start listening for RoomViewStore updates
|
// Start listening for RoomViewStore updates
|
||||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||||
|
@ -457,6 +458,7 @@ module.exports = React.createClass({
|
||||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||||
MatrixClientPeg.get().removeListener("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
MatrixClientPeg.get().removeListener("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
||||||
|
MatrixClientPeg.get().removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.removeEventListener('beforeunload', this.onPageUnload);
|
window.removeEventListener('beforeunload', this.onPageUnload);
|
||||||
|
@ -589,6 +591,10 @@ module.exports = React.createClass({
|
||||||
this._updatePreviewUrlVisibility(room);
|
this._updatePreviewUrlVisibility(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ev.getType() === "m.room.encryption") {
|
||||||
|
this._updateE2EStatus(room);
|
||||||
|
}
|
||||||
|
|
||||||
// ignore anything but real-time updates at the end of the room:
|
// ignore anything but real-time updates at the end of the room:
|
||||||
// updates from pagination will happen when the paginate completes.
|
// updates from pagination will happen when the paginate completes.
|
||||||
if (toStartOfTimeline || !data || !data.liveEvent) return;
|
if (toStartOfTimeline || !data || !data.liveEvent) return;
|
||||||
|
@ -642,6 +648,7 @@ module.exports = React.createClass({
|
||||||
this._updatePreviewUrlVisibility(room);
|
this._updatePreviewUrlVisibility(room);
|
||||||
this._loadMembersIfJoined(room);
|
this._loadMembersIfJoined(room);
|
||||||
this._calculateRecommendedVersion(room);
|
this._calculateRecommendedVersion(room);
|
||||||
|
this._updateE2EStatus(room);
|
||||||
},
|
},
|
||||||
|
|
||||||
_calculateRecommendedVersion: async function(room) {
|
_calculateRecommendedVersion: async function(room) {
|
||||||
|
@ -733,6 +740,23 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onDeviceVerificationChanged: function(userId, device) {
|
||||||
|
const room = this.state.room;
|
||||||
|
if (!room.currentState.getMember(userId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._updateE2EStatus(room);
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateE2EStatus: function(room) {
|
||||||
|
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
room.hasUnverifiedDevices().then((hasUnverifiedDevices) => {
|
||||||
|
this.setState({e2eStatus: hasUnverifiedDevices ? "warning" : "verified"});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
updateTint: function() {
|
updateTint: function() {
|
||||||
const room = this.state.room;
|
const room = this.state.room;
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
@ -1575,6 +1599,7 @@ module.exports = React.createClass({
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
oobData={this.props.oobData}
|
oobData={this.props.oobData}
|
||||||
collapsedRhs={this.props.collapsedRhs}
|
collapsedRhs={this.props.collapsedRhs}
|
||||||
|
e2eStatus={this.state.e2eStatus}
|
||||||
/>
|
/>
|
||||||
<div className="mx_RoomView_body">
|
<div className="mx_RoomView_body">
|
||||||
<div className="mx_RoomView_auxPanel">
|
<div className="mx_RoomView_auxPanel">
|
||||||
|
@ -1622,6 +1647,7 @@ module.exports = React.createClass({
|
||||||
ref="header"
|
ref="header"
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
collapsedRhs={this.props.collapsedRhs}
|
collapsedRhs={this.props.collapsedRhs}
|
||||||
|
e2eStatus={this.state.e2eStatus}
|
||||||
/>
|
/>
|
||||||
<div className="mx_RoomView_body">
|
<div className="mx_RoomView_body">
|
||||||
<div className="mx_RoomView_auxPanel">
|
<div className="mx_RoomView_auxPanel">
|
||||||
|
@ -1767,6 +1793,7 @@ module.exports = React.createClass({
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
showApps={this.state.showApps}
|
showApps={this.state.showApps}
|
||||||
uploadAllowed={this.isFileUploadAllowed}
|
uploadAllowed={this.isFileUploadAllowed}
|
||||||
|
e2eStatus={this.state.e2eStatus}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1917,6 +1944,7 @@ module.exports = React.createClass({
|
||||||
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
|
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
|
||||||
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
||||||
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
||||||
|
e2eStatus={this.state.e2eStatus}
|
||||||
/>
|
/>
|
||||||
<MainSplit panel={rightPanel} collapsedRhs={this.props.collapsedRhs}>
|
<MainSplit panel={rightPanel} collapsedRhs={this.props.collapsedRhs}>
|
||||||
<div className={fadableSectionClasses}>
|
<div className={fadableSectionClasses}>
|
||||||
|
|
39
src/components/views/rooms/E2EIcon.js
Normal file
39
src/components/views/rooms/E2EIcon.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
export default function(props) {
|
||||||
|
const isWarning = props.status === "warning";
|
||||||
|
const isVerified = props.status === "verified";
|
||||||
|
const e2eIconClasses = classNames({
|
||||||
|
mx_E2EIcon: true,
|
||||||
|
mx_E2EIcon_warning: isWarning,
|
||||||
|
mx_E2EIcon_verified: isVerified,
|
||||||
|
}, props.className);
|
||||||
|
let e2eTitle;
|
||||||
|
if (isWarning) {
|
||||||
|
e2eTitle = props.isUser ?
|
||||||
|
_t("Some devices for this user are not trusted") :
|
||||||
|
_t("Some devices in this encrypted room are not trusted");
|
||||||
|
} else if (isVerified) {
|
||||||
|
e2eTitle = props.isUser ?
|
||||||
|
_t("All devices for this user are trusted") :
|
||||||
|
_t("All devices in this encrypted room are trusted");
|
||||||
|
}
|
||||||
|
return (<div className={e2eIconClasses} title={e2eTitle} />);
|
||||||
|
}
|
|
@ -459,17 +459,21 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
|
|
||||||
// event is encrypted, display padlock corresponding to whether or not it is verified
|
// event is encrypted, display padlock corresponding to whether or not it is verified
|
||||||
if (ev.isEncrypted()) {
|
if (ev.isEncrypted()) {
|
||||||
return this.state.verified ? <E2ePadlockVerified {...props} /> : <E2ePadlockUnverified {...props} />;
|
if (this.state.verified) {
|
||||||
|
return; // no icon for verified
|
||||||
|
} else {
|
||||||
|
return (<E2ePadlockUnverified {...props} />);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.matrixClient.isRoomEncrypted(ev.getRoomId())) {
|
if (this.props.matrixClient.isRoomEncrypted(ev.getRoomId())) {
|
||||||
// else if room is encrypted
|
// else if room is encrypted
|
||||||
// and event is being encrypted or is not_sent (Unknown Devices/Network Error)
|
// and event is being encrypted or is not_sent (Unknown Devices/Network Error)
|
||||||
if (ev.status === EventStatus.ENCRYPTING) {
|
if (ev.status === EventStatus.ENCRYPTING) {
|
||||||
return <E2ePadlockEncrypting {...props} />;
|
return;
|
||||||
}
|
}
|
||||||
if (ev.status === EventStatus.NOT_SENT) {
|
if (ev.status === EventStatus.NOT_SENT) {
|
||||||
return <E2ePadlockNotSent {...props} />;
|
return;
|
||||||
}
|
}
|
||||||
// if the event is not encrypted, but it's an e2e room, show the open padlock
|
// if the event is not encrypted, but it's an e2e room, show the open padlock
|
||||||
return <E2ePadlockUnencrypted {...props} />;
|
return <E2ePadlockUnencrypted {...props} />;
|
||||||
|
@ -767,57 +771,29 @@ module.exports.haveTileForEvent = function(e) {
|
||||||
|
|
||||||
function E2ePadlockUndecryptable(props) {
|
function E2ePadlockUndecryptable(props) {
|
||||||
return (
|
return (
|
||||||
<E2ePadlock alt={_t("Undecryptable")}
|
<E2ePadlock title={_t("Undecryptable")} icon="undecryptable" />
|
||||||
src={require("../../../../res/img/e2e-blocked.svg")} width="12" height="12"
|
|
||||||
style={{ marginLeft: "-1px" }} {...props} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function E2ePadlockEncrypting(props) {
|
|
||||||
return (
|
|
||||||
<E2ePadlock alt={_t("Encrypting")}
|
|
||||||
src={require("../../../../res/img/e2e-encrypting.svg")} width="10" height="12"
|
|
||||||
{...props} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function E2ePadlockNotSent(props) {
|
|
||||||
return (
|
|
||||||
<E2ePadlock alt={_t("Encrypted, not sent")}
|
|
||||||
src={require("../../../../res/img/e2e-not_sent.svg")} width="10" height="12"
|
|
||||||
{...props} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function E2ePadlockVerified(props) {
|
|
||||||
return (
|
|
||||||
<E2ePadlock alt={_t("Encrypted by a verified device")}
|
|
||||||
src={require("../../../../res/img/e2e-verified.svg")} width="10" height="12"
|
|
||||||
{...props} />
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function E2ePadlockUnverified(props) {
|
function E2ePadlockUnverified(props) {
|
||||||
return (
|
return (
|
||||||
<E2ePadlock alt={_t("Encrypted by an unverified device")}
|
<E2ePadlock title={_t("Encrypted by an unverified device")} icon="unverified" />
|
||||||
src={require("../../../../res/img/e2e-warning.svg")} width="15" height="12"
|
|
||||||
style={{ marginLeft: "-2px" }} {...props} />
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function E2ePadlockUnencrypted(props) {
|
function E2ePadlockUnencrypted(props) {
|
||||||
return (
|
return (
|
||||||
<E2ePadlock alt={_t("Unencrypted message")}
|
<E2ePadlock title={_t("Unencrypted message")} icon="unencrypted" />
|
||||||
src={require("../../../../res/img/e2e-unencrypted.svg")} width="12" height="12"
|
|
||||||
{...props} />
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function E2ePadlock(props) {
|
function E2ePadlock(props) {
|
||||||
if (SettingsStore.getValue("alwaysShowEncryptionIcons")) {
|
if (SettingsStore.getValue("alwaysShowEncryptionIcons")) {
|
||||||
return <img className="mx_EventTile_e2eIcon" {...props} />;
|
return <div
|
||||||
|
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${props.icon}`}
|
||||||
|
title={props.title} onClick={props.onClick} />;
|
||||||
} else {
|
} else {
|
||||||
return <img className="mx_EventTile_e2eIcon mx_EventTile_e2eIcon_hidden" {...props} />;
|
return <div className="mx_EventTile_e2eIcon mx_EventTile_e2eIcon_hidden" onClick={props.onClick} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,32 +18,18 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default class MemberDeviceInfo extends React.Component {
|
export default class MemberDeviceInfo extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
let indicator = null;
|
|
||||||
const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
|
const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
|
||||||
|
const iconClasses = classNames({
|
||||||
if (this.props.device.isBlocked()) {
|
mx_MemberDeviceInfo_icon: true,
|
||||||
indicator = (
|
mx_MemberDeviceInfo_icon_blacklisted: this.props.device.isBlocked(),
|
||||||
<div className="mx_MemberDeviceInfo_blacklisted">
|
mx_MemberDeviceInfo_icon_verified: this.props.device.isVerified(),
|
||||||
<img src={require("../../../../res/img/e2e-blocked.svg")} width="12" height="12" style={{ marginLeft: "-1px" }} alt={_t("Blacklisted")} />
|
mx_MemberDeviceInfo_icon_unverified: this.props.device.isUnverified(),
|
||||||
</div>
|
});
|
||||||
);
|
const indicator = (<div className={iconClasses} />);
|
||||||
} else if (this.props.device.isVerified()) {
|
|
||||||
indicator = (
|
|
||||||
<div className="mx_MemberDeviceInfo_verified">
|
|
||||||
<img src={require("../../../../res/img/e2e-verified.svg")} width="10" height="12" alt={_t("Verified")} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
indicator = (
|
|
||||||
<div className="mx_MemberDeviceInfo_unverified">
|
|
||||||
<img src={require("../../../../res/img/e2e-warning.svg")} width="15" height="12" style={{ marginLeft: "-2px" }} alt={_t("Unverified")} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const deviceName = this.props.device.ambiguous ?
|
const deviceName = this.props.device.ambiguous ?
|
||||||
(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();
|
||||||
|
@ -52,10 +38,10 @@ export default class MemberDeviceInfo extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberDeviceInfo"
|
<div className="mx_MemberDeviceInfo"
|
||||||
title={_t("device id: ") + this.props.device.deviceId} >
|
title={_t("device id: ") + this.props.device.deviceId} >
|
||||||
|
{ indicator }
|
||||||
<div className="mx_MemberDeviceInfo_deviceInfo">
|
<div className="mx_MemberDeviceInfo_deviceInfo">
|
||||||
<div className="mx_MemberDeviceInfo_deviceId">
|
<div className="mx_MemberDeviceInfo_deviceId">
|
||||||
{ deviceName }
|
{ deviceName }
|
||||||
{ indicator }
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DeviceVerifyButtons userId={this.props.userId} device={this.props.device} />
|
<DeviceVerifyButtons userId={this.props.userId} device={this.props.device} />
|
||||||
|
|
|
@ -43,6 +43,7 @@ import RoomViewStore from '../../../stores/RoomViewStore';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import MultiInviter from "../../../utils/MultiInviter";
|
import MultiInviter from "../../../utils/MultiInviter";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import E2EIcon from "./E2EIcon";
|
||||||
|
|
||||||
module.exports = withMatrixClient(React.createClass({
|
module.exports = withMatrixClient(React.createClass({
|
||||||
displayName: 'MemberInfo',
|
displayName: 'MemberInfo',
|
||||||
|
@ -153,11 +154,19 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
// Promise.resolve to handle transition from static result to promise; can be removed
|
// Promise.resolve to handle transition from static result to promise; can be removed
|
||||||
// in future
|
// in future
|
||||||
Promise.resolve(this.props.matrixClient.getStoredDevicesForUser(userId)).then((devices) => {
|
Promise.resolve(this.props.matrixClient.getStoredDevicesForUser(userId)).then((devices) => {
|
||||||
this.setState({devices: devices});
|
this.setState({
|
||||||
|
devices: devices,
|
||||||
|
e2eStatus: this._getE2EStatus(devices),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_getE2EStatus: function(devices) {
|
||||||
|
const hasUnverifiedDevice = devices.some((device) => device.isUnverified());
|
||||||
|
return hasUnverifiedDevice ? "warning" : "verified";
|
||||||
|
},
|
||||||
|
|
||||||
onRoom: function(room) {
|
onRoom: function(room) {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
},
|
},
|
||||||
|
@ -234,8 +243,13 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
// we got cancelled - presumably a different user now
|
// we got cancelled - presumably a different user now
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self._disambiguateDevices(devices);
|
self._disambiguateDevices(devices);
|
||||||
self.setState({devicesLoading: false, devices: devices});
|
self.setState({
|
||||||
|
devicesLoading: false,
|
||||||
|
devices: devices,
|
||||||
|
e2eStatus: self._getE2EStatus(devices),
|
||||||
|
});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
console.log("Error downloading devices", err);
|
console.log("Error downloading devices", err);
|
||||||
self.setState({devicesLoading: false});
|
self.setState({devicesLoading: false});
|
||||||
|
@ -965,6 +979,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 }
|
||||||
<EmojiText element="h2">{ memberName }</EmojiText>
|
<EmojiText element="h2">{ memberName }</EmojiText>
|
||||||
</div>
|
</div>
|
||||||
{ avatarElement }
|
{ avatarElement }
|
||||||
|
|
|
@ -26,6 +26,9 @@ import RoomViewStore from '../../../stores/RoomViewStore';
|
||||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||||
import Stickerpicker from './Stickerpicker';
|
import Stickerpicker from './Stickerpicker';
|
||||||
import { makeRoomPermalink } from '../../../matrix-to';
|
import { makeRoomPermalink } from '../../../matrix-to';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import E2EIcon from './E2EIcon';
|
||||||
|
|
||||||
const formatButtonList = [
|
const formatButtonList = [
|
||||||
_td("bold"),
|
_td("bold"),
|
||||||
|
@ -316,25 +319,14 @@ export default class MessageComposer extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let e2eImg; let e2eTitle; let e2eClass;
|
if (this.props.e2eStatus) {
|
||||||
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
controls.push(<E2EIcon
|
||||||
if (roomIsEncrypted) {
|
status={this.props.e2eStatus}
|
||||||
// FIXME: show a /!\ if there are untrusted devices in the room...
|
key="e2eIcon"
|
||||||
e2eImg = require("../../../../res/img/e2e-verified.svg");
|
className="mx_MessageComposer_e2eIcon" />
|
||||||
e2eTitle = _t('Encrypted room');
|
);
|
||||||
e2eClass = 'mx_MessageComposer_e2eIcon';
|
|
||||||
} else {
|
|
||||||
e2eImg = require("../../../../res/img/e2e-unencrypted.svg");
|
|
||||||
e2eTitle = _t('Unencrypted room');
|
|
||||||
e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
controls.push(
|
|
||||||
<img key="e2eIcon" className={e2eClass} src={e2eImg} width="12" height="12"
|
|
||||||
alt={e2eTitle} title={e2eTitle}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
let callButton;
|
let callButton;
|
||||||
let videoCallButton;
|
let videoCallButton;
|
||||||
let hangupButton;
|
let hangupButton;
|
||||||
|
@ -413,6 +405,7 @@ export default class MessageComposer extends React.Component {
|
||||||
key="controls_formatting" />
|
key="controls_formatting" />
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
||||||
let placeholderText;
|
let placeholderText;
|
||||||
if (this.state.isQuoting) {
|
if (this.state.isQuoting) {
|
||||||
if (roomIsEncrypted) {
|
if (roomIsEncrypted) {
|
||||||
|
@ -509,9 +502,13 @@ export default class MessageComposer extends React.Component {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wrapperClasses = classNames({
|
||||||
|
mx_MessageComposer_wrapper: true,
|
||||||
|
mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus,
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div className="mx_MessageComposer">
|
<div className="mx_MessageComposer">
|
||||||
<div className="mx_MessageComposer_wrapper">
|
<div className={wrapperClasses}>
|
||||||
<div className="mx_MessageComposer_row">
|
<div className="mx_MessageComposer_row">
|
||||||
{ controls }
|
{ controls }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -33,6 +33,7 @@ import ManageIntegsButton from '../elements/ManageIntegsButton';
|
||||||
import {CancelButton} from './SimpleRoomHeader';
|
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';
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
linkifyMatrix(linkify);
|
||||||
|
|
||||||
|
@ -52,6 +53,7 @@ module.exports = React.createClass({
|
||||||
onSearchClick: PropTypes.func,
|
onSearchClick: PropTypes.func,
|
||||||
onLeaveClick: PropTypes.func,
|
onLeaveClick: PropTypes.func,
|
||||||
onCancelClick: PropTypes.func,
|
onCancelClick: PropTypes.func,
|
||||||
|
e2eStatus: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -237,6 +239,10 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const e2eIcon = this.props.e2eStatus ?
|
||||||
|
<E2EIcon status={this.props.e2eStatus} /> :
|
||||||
|
undefined;
|
||||||
|
|
||||||
if (this.props.onCancelClick) {
|
if (this.props.onCancelClick) {
|
||||||
cancelButton = <CancelButton onClick={this.props.onCancelClick} />;
|
cancelButton = <CancelButton onClick={this.props.onCancelClick} />;
|
||||||
}
|
}
|
||||||
|
@ -413,6 +419,7 @@ module.exports = React.createClass({
|
||||||
<div className={"mx_RoomHeader light-panel " + (this.props.editing ? "mx_RoomHeader_editing" : "")}>
|
<div className={"mx_RoomHeader light-panel " + (this.props.editing ? "mx_RoomHeader_editing" : "")}>
|
||||||
<div className="mx_RoomHeader_wrapper">
|
<div className="mx_RoomHeader_wrapper">
|
||||||
<div className="mx_RoomHeader_avatar">{ roomAvatar }</div>
|
<div className="mx_RoomHeader_avatar">{ roomAvatar }</div>
|
||||||
|
{ e2eIcon }
|
||||||
{ name }
|
{ name }
|
||||||
{ topicElement }
|
{ topicElement }
|
||||||
{ spinner }
|
{ spinner }
|
||||||
|
|
|
@ -571,6 +571,10 @@
|
||||||
" (unsupported)": " (unsupported)",
|
" (unsupported)": " (unsupported)",
|
||||||
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
|
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
|
||||||
"Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.",
|
"Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.",
|
||||||
|
"Some devices for this user are not trusted": "Some devices for this user are not trusted",
|
||||||
|
"Some devices in this encrypted room are not trusted": "Some devices in this encrypted room are not trusted",
|
||||||
|
"All devices for this user are trusted": "All devices for this user are trusted",
|
||||||
|
"All devices in this encrypted room are trusted": "All devices in this encrypted room are trusted",
|
||||||
"This event could not be displayed": "This event could not be displayed",
|
"This event could not be displayed": "This event could not be displayed",
|
||||||
"%(senderName)s sent an image": "%(senderName)s sent an image",
|
"%(senderName)s sent an image": "%(senderName)s sent an image",
|
||||||
"%(senderName)s sent a video": "%(senderName)s sent a video",
|
"%(senderName)s sent a video": "%(senderName)s sent a video",
|
||||||
|
@ -582,16 +586,10 @@
|
||||||
"Key request sent.": "Key request sent.",
|
"Key request sent.": "Key request sent.",
|
||||||
"<requestLink>Re-request encryption keys</requestLink> from your other devices.": "<requestLink>Re-request encryption keys</requestLink> from your other devices.",
|
"<requestLink>Re-request encryption keys</requestLink> from your other devices.": "<requestLink>Re-request encryption keys</requestLink> from your other devices.",
|
||||||
"Undecryptable": "Undecryptable",
|
"Undecryptable": "Undecryptable",
|
||||||
"Encrypting": "Encrypting",
|
|
||||||
"Encrypted, not sent": "Encrypted, not sent",
|
|
||||||
"Encrypted by a verified device": "Encrypted by a verified device",
|
|
||||||
"Encrypted by an unverified device": "Encrypted by an unverified device",
|
"Encrypted by an unverified device": "Encrypted by an unverified device",
|
||||||
"Unencrypted message": "Unencrypted message",
|
"Unencrypted message": "Unencrypted message",
|
||||||
"Please select the destination room for this message": "Please select the destination room for this message",
|
"Please select the destination room for this message": "Please select the destination room for this message",
|
||||||
"Scroll to bottom of page": "Scroll to bottom of page",
|
"Scroll to bottom of page": "Scroll to bottom of page",
|
||||||
"Blacklisted": "Blacklisted",
|
|
||||||
"Verified": "Verified",
|
|
||||||
"Unverified": "Unverified",
|
|
||||||
"device id: ": "device id: ",
|
"device id: ": "device id: ",
|
||||||
"Disinvite": "Disinvite",
|
"Disinvite": "Disinvite",
|
||||||
"Kick": "Kick",
|
"Kick": "Kick",
|
||||||
|
@ -643,8 +641,6 @@
|
||||||
"Are you sure you want to upload the following files?": "Are you sure you want to upload the following files?",
|
"Are you sure you want to upload the following files?": "Are you sure you want to upload the following files?",
|
||||||
"The following files cannot be uploaded:": "The following files cannot be uploaded:",
|
"The following files cannot be uploaded:": "The following files cannot be uploaded:",
|
||||||
"Upload Files": "Upload Files",
|
"Upload Files": "Upload Files",
|
||||||
"Encrypted room": "Encrypted room",
|
|
||||||
"Unencrypted room": "Unencrypted room",
|
|
||||||
"Hangup": "Hangup",
|
"Hangup": "Hangup",
|
||||||
"Voice call": "Voice call",
|
"Voice call": "Voice call",
|
||||||
"Video call": "Video call",
|
"Video call": "Video call",
|
||||||
|
@ -1437,6 +1433,7 @@
|
||||||
"Users": "Users",
|
"Users": "Users",
|
||||||
"unknown device": "unknown device",
|
"unknown device": "unknown device",
|
||||||
"NOT verified": "NOT verified",
|
"NOT verified": "NOT verified",
|
||||||
|
"Blacklisted": "Blacklisted",
|
||||||
"verified": "verified",
|
"verified": "verified",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Verification": "Verification",
|
"Verification": "Verification",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue