Merge pull request #2557 from matrix-org/bwindels/e2eicons

Add e2e icon to room header/composer/member info, more ...
This commit is contained in:
Bruno Windels 2019-02-01 22:02:28 +00:00 committed by GitHub
commit 45e982ac13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 251 additions and 94 deletions

View file

@ -107,6 +107,7 @@
@import "./views/rooms/_AppsDrawer.scss";
@import "./views/rooms/_Autocomplete.scss";
@import "./views/rooms/_AuxPanel.scss";
@import "./views/rooms/_E2EIcon.scss";
@import "./views/rooms/_EntityTile.scss";
@import "./views/rooms/_EventTile.scss";
@import "./views/rooms/_JumpToBottomButton.scss";

View file

@ -121,7 +121,7 @@ limitations under the License.
.mx_RoomStatusBar_connectionLostBar img {
padding-left: 10px;
padding-right: 22px;
padding-right: 10px;
vertical-align: middle;
float: left;
}

View 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;
}

View file

@ -281,9 +281,24 @@ limitations under the License.
.mx_EventTile_e2eIcon {
display: block;
position: absolute;
top: 9px;
top: 8px;
left: 46px;
width: 15px;
height: 15px;
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 {

View file

@ -20,6 +20,25 @@ limitations under the License.
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 {
display: flex;
flex-direction: column;

View file

@ -26,6 +26,10 @@ limitations under the License.
display: flex;
}
.mx_MemberInfo_name > .mx_E2EIcon {
margin-left: 0;
}
.mx_MemberInfo_cancel {
height: 16px;
padding: 10px 15px;

View file

@ -23,6 +23,10 @@ limitations under the License.
padding-left: 84px;
}
.mx_MessageComposer_wrapper.mx_MessageComposer_hasE2EIcon {
padding-left: 109px;
}
.mx_MessageComposer_replaced_wrapper {
margin-left: auto;
margin-right: auto;
@ -71,9 +75,10 @@ limitations under the License.
width: 100%;
}
.mx_MessageComposer_e2eIcon {
.mx_MessageComposer_e2eIcon.mx_E2EIcon {
position: absolute;
left: 60px;
background-color: $composer-e2e-icon-color;
}
.mx_MessageComposer_noperm_error {

View 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

View 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

View 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

View 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

View 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

View file

@ -142,6 +142,8 @@ $roomheader-addroom-color: #91A1C0;
$roomtopic-color: #9fa9ba;
$eventtile-meta-color: $roomtopic-color;
$composer-e2e-icon-color: #c9ced6;
// ********************
$roomtile-name-color: #61708b;

View file

@ -134,6 +134,9 @@ $roomheader-color: $primary-fg-color;
$roomheader-addroom-color: $primary-bg-color;
$roomtopic-color: $settings-grey-fg-color;
$eventtile-meta-color: $roomtopic-color;
$composer-e2e-icon-color: #c9ced6;
// ********************
$roomtile-name-color: rgba(69, 69, 69, 0.8);

View file

@ -290,7 +290,7 @@ module.exports = React.createClass({
}
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 className="mx_RoomStatusBar_connectionLostBar_title">
{ title }
@ -309,7 +309,7 @@ module.exports = React.createClass({
if (this._shouldShowConnectionError()) {
return (
<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 className="mx_RoomStatusBar_connectionLostBar_title">
{ _t('Connectivity to the server has been lost.') }

View file

@ -168,6 +168,7 @@ module.exports = React.createClass({
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
MatrixClientPeg.get().on("accountData", this.onAccountData);
MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus);
MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged);
this._fetchMediaConfig();
// Start listening for RoomViewStore updates
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
@ -457,6 +458,7 @@ module.exports = React.createClass({
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
MatrixClientPeg.get().removeListener("crypto.keyBackupStatus", this.onKeyBackupStatus);
MatrixClientPeg.get().removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
}
window.removeEventListener('beforeunload', this.onPageUnload);
@ -589,6 +591,10 @@ module.exports = React.createClass({
this._updatePreviewUrlVisibility(room);
}
if (ev.getType() === "m.room.encryption") {
this._updateE2EStatus(room);
}
// ignore anything but real-time updates at the end of the room:
// updates from pagination will happen when the paginate completes.
if (toStartOfTimeline || !data || !data.liveEvent) return;
@ -642,6 +648,7 @@ module.exports = React.createClass({
this._updatePreviewUrlVisibility(room);
this._loadMembersIfJoined(room);
this._calculateRecommendedVersion(room);
this._updateE2EStatus(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() {
const room = this.state.room;
if (!room) return;
@ -1575,6 +1599,7 @@ module.exports = React.createClass({
room={this.state.room}
oobData={this.props.oobData}
collapsedRhs={this.props.collapsedRhs}
e2eStatus={this.state.e2eStatus}
/>
<div className="mx_RoomView_body">
<div className="mx_RoomView_auxPanel">
@ -1622,6 +1647,7 @@ module.exports = React.createClass({
ref="header"
room={this.state.room}
collapsedRhs={this.props.collapsedRhs}
e2eStatus={this.state.e2eStatus}
/>
<div className="mx_RoomView_body">
<div className="mx_RoomView_auxPanel">
@ -1767,6 +1793,7 @@ module.exports = React.createClass({
disabled={this.props.disabled}
showApps={this.state.showApps}
uploadAllowed={this.isFileUploadAllowed}
e2eStatus={this.state.e2eStatus}
/>;
}
@ -1917,6 +1944,7 @@ module.exports = React.createClass({
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
e2eStatus={this.state.e2eStatus}
/>
<MainSplit panel={rightPanel} collapsedRhs={this.props.collapsedRhs}>
<div className={fadableSectionClasses}>

View 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} />);
}

View file

@ -459,17 +459,21 @@ module.exports = withMatrixClient(React.createClass({
// event is encrypted, display padlock corresponding to whether or not it is verified
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())) {
// else if room is encrypted
// and event is being encrypted or is not_sent (Unknown Devices/Network Error)
if (ev.status === EventStatus.ENCRYPTING) {
return <E2ePadlockEncrypting {...props} />;
return;
}
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
return <E2ePadlockUnencrypted {...props} />;
@ -767,57 +771,29 @@ module.exports.haveTileForEvent = function(e) {
function E2ePadlockUndecryptable(props) {
return (
<E2ePadlock alt={_t("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} />
<E2ePadlock title={_t("Undecryptable")} icon="undecryptable" />
);
}
function E2ePadlockUnverified(props) {
return (
<E2ePadlock alt={_t("Encrypted by an unverified device")}
src={require("../../../../res/img/e2e-warning.svg")} width="15" height="12"
style={{ marginLeft: "-2px" }} {...props} />
<E2ePadlock title={_t("Encrypted by an unverified device")} icon="unverified" />
);
}
function E2ePadlockUnencrypted(props) {
return (
<E2ePadlock alt={_t("Unencrypted message")}
src={require("../../../../res/img/e2e-unencrypted.svg")} width="12" height="12"
{...props} />
<E2ePadlock title={_t("Unencrypted message")} icon="unencrypted" />
);
}
function E2ePadlock(props) {
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 {
return <img className="mx_EventTile_e2eIcon mx_EventTile_e2eIcon_hidden" {...props} />;
return <div className="mx_EventTile_e2eIcon mx_EventTile_e2eIcon_hidden" onClick={props.onClick} />;
}
}

View file

@ -18,32 +18,18 @@ import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import classNames from 'classnames';
export default class MemberDeviceInfo extends React.Component {
render() {
let indicator = null;
const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
if (this.props.device.isBlocked()) {
indicator = (
<div className="mx_MemberDeviceInfo_blacklisted">
<img src={require("../../../../res/img/e2e-blocked.svg")} width="12" height="12" style={{ marginLeft: "-1px" }} alt={_t("Blacklisted")} />
</div>
);
} 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 iconClasses = classNames({
mx_MemberDeviceInfo_icon: true,
mx_MemberDeviceInfo_icon_blacklisted: this.props.device.isBlocked(),
mx_MemberDeviceInfo_icon_verified: this.props.device.isVerified(),
mx_MemberDeviceInfo_icon_unverified: this.props.device.isUnverified(),
});
const indicator = (<div className={iconClasses} />);
const deviceName = this.props.device.ambiguous ?
(this.props.device.getDisplayName() ? this.props.device.getDisplayName() : "") + " (" + this.props.device.deviceId + ")" :
this.props.device.getDisplayName();
@ -52,10 +38,10 @@ export default class MemberDeviceInfo extends React.Component {
return (
<div className="mx_MemberDeviceInfo"
title={_t("device id: ") + this.props.device.deviceId} >
{ indicator }
<div className="mx_MemberDeviceInfo_deviceInfo">
<div className="mx_MemberDeviceInfo_deviceId">
{ deviceName }
{ indicator }
</div>
</div>
<DeviceVerifyButtons userId={this.props.userId} device={this.props.device} />

View file

@ -43,6 +43,7 @@ import RoomViewStore from '../../../stores/RoomViewStore';
import SdkConfig from '../../../SdkConfig';
import MultiInviter from "../../../utils/MultiInviter";
import SettingsStore from "../../../settings/SettingsStore";
import E2EIcon from "./E2EIcon";
module.exports = withMatrixClient(React.createClass({
displayName: 'MemberInfo',
@ -153,11 +154,19 @@ module.exports = withMatrixClient(React.createClass({
// Promise.resolve to handle transition from static result to promise; can be removed
// in future
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) {
this.forceUpdate();
},
@ -234,8 +243,13 @@ module.exports = withMatrixClient(React.createClass({
// we got cancelled - presumably a different user now
return;
}
self._disambiguateDevices(devices);
self.setState({devicesLoading: false, devices: devices});
self.setState({
devicesLoading: false,
devices: devices,
e2eStatus: self._getE2EStatus(devices),
});
}, function(err) {
console.log("Error downloading devices", err);
self.setState({devicesLoading: false});
@ -965,6 +979,7 @@ module.exports = withMatrixClient(React.createClass({
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}>
<img src={require("../../../../res/img/minimise.svg")} width="10" height="16" className="mx_filterFlipColor" alt={_t('Close')} />
</AccessibleButton>
{ this.state.e2eStatus ? <E2EIcon status={this.state.e2eStatus} isUser={true} /> : undefined }
<EmojiText element="h2">{ memberName }</EmojiText>
</div>
{ avatarElement }

View file

@ -26,6 +26,9 @@ import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink } from '../../../matrix-to';
import classNames from 'classnames';
import E2EIcon from './E2EIcon';
const formatButtonList = [
_td("bold"),
@ -316,25 +319,14 @@ export default class MessageComposer extends React.Component {
);
}
let e2eImg; let e2eTitle; let e2eClass;
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
if (roomIsEncrypted) {
// FIXME: show a /!\ if there are untrusted devices in the room...
e2eImg = require("../../../../res/img/e2e-verified.svg");
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';
if (this.props.e2eStatus) {
controls.push(<E2EIcon
status={this.props.e2eStatus}
key="e2eIcon"
className="mx_MessageComposer_e2eIcon" />
);
}
controls.push(
<img key="e2eIcon" className={e2eClass} src={e2eImg} width="12" height="12"
alt={e2eTitle} title={e2eTitle}
/>,
);
let callButton;
let videoCallButton;
let hangupButton;
@ -413,6 +405,7 @@ export default class MessageComposer extends React.Component {
key="controls_formatting" />
) : null;
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
let placeholderText;
if (this.state.isQuoting) {
if (roomIsEncrypted) {
@ -509,9 +502,13 @@ export default class MessageComposer extends React.Component {
</div>;
}
const wrapperClasses = classNames({
mx_MessageComposer_wrapper: true,
mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus,
});
return (
<div className="mx_MessageComposer">
<div className="mx_MessageComposer_wrapper">
<div className={wrapperClasses}>
<div className="mx_MessageComposer_row">
{ controls }
</div>

View file

@ -33,6 +33,7 @@ import ManageIntegsButton from '../elements/ManageIntegsButton';
import {CancelButton} from './SimpleRoomHeader';
import SettingsStore from "../../../settings/SettingsStore";
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
import E2EIcon from './E2EIcon';
linkifyMatrix(linkify);
@ -52,6 +53,7 @@ module.exports = React.createClass({
onSearchClick: PropTypes.func,
onLeaveClick: PropTypes.func,
onCancelClick: PropTypes.func,
e2eStatus: PropTypes.string,
},
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) {
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_wrapper">
<div className="mx_RoomHeader_avatar">{ roomAvatar }</div>
{ e2eIcon }
{ name }
{ topicElement }
{ spinner }

View file

@ -571,6 +571,10 @@
" (unsupported)": " (unsupported)",
"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.",
"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",
"%(senderName)s sent an image": "%(senderName)s sent an image",
"%(senderName)s sent a video": "%(senderName)s sent a video",
@ -582,16 +586,10 @@
"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.",
"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",
"Unencrypted message": "Unencrypted 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",
"Blacklisted": "Blacklisted",
"Verified": "Verified",
"Unverified": "Unverified",
"device id: ": "device id: ",
"Disinvite": "Disinvite",
"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?",
"The following files cannot be uploaded:": "The following files cannot be uploaded:",
"Upload Files": "Upload Files",
"Encrypted room": "Encrypted room",
"Unencrypted room": "Unencrypted room",
"Hangup": "Hangup",
"Voice call": "Voice call",
"Video call": "Video call",
@ -1437,6 +1433,7 @@
"Users": "Users",
"unknown device": "unknown device",
"NOT verified": "NOT verified",
"Blacklisted": "Blacklisted",
"verified": "verified",
"Name": "Name",
"Verification": "Verification",