Merge remote-tracking branch 'origin/experimental' into dbkr/sas
This commit is contained in:
commit
970880737e
83 changed files with 2920 additions and 1437 deletions
|
@ -40,38 +40,50 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
hasStatus: this.hasStatus,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) {
|
||||
throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user");
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
|
||||
|
||||
if (this.props.member.user) {
|
||||
this.setState({message: this.props.member.user._unstable_statusMessage});
|
||||
} else {
|
||||
this.setState({message: ""});
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents);
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
|
||||
}
|
||||
|
||||
_onRoomStateEvents = (ev, state) => {
|
||||
if (ev.getStateKey() !== MatrixClientPeg.get().getUserId()) return;
|
||||
if (ev.getType() !== "im.vector.user_status") return;
|
||||
// TODO: We should be relying on `this.props.member.user._unstable_statusMessage`
|
||||
// We don't currently because the js-sdk doesn't emit a specific event for this
|
||||
// change, and we don't want to race it. This should be improved when we rip out
|
||||
// the im.vector.user_status stuff and replace it with a complete solution.
|
||||
this.setState({message: ev.getContent()["status"]});
|
||||
componentWillUmount() {
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.removeListener(
|
||||
"User._unstable_statusMessage",
|
||||
this._onStatusMessageCommitted,
|
||||
);
|
||||
}
|
||||
|
||||
get hasStatus() {
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
return !!user._unstable_statusMessage;
|
||||
}
|
||||
|
||||
_onStatusMessageCommitted = () => {
|
||||
// The `User` object has observed a status message change.
|
||||
this.setState({
|
||||
hasStatus: this.hasStatus,
|
||||
});
|
||||
};
|
||||
|
||||
_onClick = (e) => {
|
||||
|
@ -79,42 +91,43 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
|
||||
const elementRect = e.target.getBoundingClientRect();
|
||||
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const x = (elementRect.left + window.pageXOffset) - (elementRect.width / 2) + 3;
|
||||
const chevronOffset = 12;
|
||||
let y = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
|
||||
y = y - (chevronOffset + 4); // where 4 is 1/4 the height of the chevron
|
||||
const x = (elementRect.left + window.pageXOffset);
|
||||
const chevronWidth = 16; // See .mx_ContextualMenu_chevron_bottom
|
||||
const chevronOffset = (elementRect.width - chevronWidth) / 2;
|
||||
const chevronMargin = 1; // Add some spacing away from target
|
||||
const y = elementRect.top + window.pageYOffset - chevronMargin;
|
||||
|
||||
ContextualMenu.createMenu(StatusMessageContextMenu, {
|
||||
chevronOffset: chevronOffset,
|
||||
chevronFace: 'bottom',
|
||||
left: x,
|
||||
top: y,
|
||||
menuWidth: 190,
|
||||
menuWidth: 226,
|
||||
user: this.props.member.user,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
return <MemberAvatar member={this.props.member}
|
||||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
resizeMethod={this.props.resizeMethod} />;
|
||||
}
|
||||
const avatar = <MemberAvatar
|
||||
member={this.props.member}
|
||||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
resizeMethod={this.props.resizeMethod}
|
||||
/>;
|
||||
|
||||
const hasStatus = this.props.member.user ? !!this.props.member.user._unstable_statusMessage : false;
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
const classes = classNames({
|
||||
"mx_MemberStatusMessageAvatar": true,
|
||||
"mx_MemberStatusMessageAvatar_hasStatus": hasStatus,
|
||||
"mx_MemberStatusMessageAvatar_hasStatus": this.state.hasStatus,
|
||||
});
|
||||
|
||||
return <AccessibleButton onClick={this._onClick} className={classes} element="div">
|
||||
<MemberAvatar member={this.props.member}
|
||||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
resizeMethod={this.props.resizeMethod} />
|
||||
return <AccessibleButton className={classes}
|
||||
element="div" onClick={this._onClick}
|
||||
>
|
||||
{avatar}
|
||||
</AccessibleButton>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,69 +18,125 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default class StatusMessageContextMenu extends React.Component {
|
||||
static propTypes = {
|
||||
// js-sdk User object. Not required because it might not exist.
|
||||
user: PropTypes.object,
|
||||
// True when waiting for status change to complete.
|
||||
waiting: false,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
message: props.user ? props.user._unstable_statusMessage : "",
|
||||
message: this.comittedStatusMessage,
|
||||
};
|
||||
}
|
||||
|
||||
_onClearClick = async (e) => {
|
||||
await MatrixClientPeg.get()._unstable_setStatusMessage("");
|
||||
this.setState({message: ""});
|
||||
componentWillMount() {
|
||||
const { user } = this.props;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
|
||||
}
|
||||
|
||||
componentWillUmount() {
|
||||
const { user } = this.props;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.removeListener(
|
||||
"User._unstable_statusMessage",
|
||||
this._onStatusMessageCommitted,
|
||||
);
|
||||
}
|
||||
|
||||
get comittedStatusMessage() {
|
||||
return this.props.user ? this.props.user._unstable_statusMessage : "";
|
||||
}
|
||||
|
||||
_onStatusMessageCommitted = () => {
|
||||
// The `User` object has observed a status message change.
|
||||
this.setState({
|
||||
message: this.comittedStatusMessage,
|
||||
waiting: false,
|
||||
});
|
||||
};
|
||||
|
||||
_onClearClick = (e) => {
|
||||
MatrixClientPeg.get()._unstable_setStatusMessage("");
|
||||
this.setState({
|
||||
waiting: true,
|
||||
});
|
||||
};
|
||||
|
||||
_onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
MatrixClientPeg.get()._unstable_setStatusMessage(this.state.message);
|
||||
this.setState({
|
||||
waiting: true,
|
||||
});
|
||||
};
|
||||
|
||||
_onStatusChange = (e) => {
|
||||
this.setState({message: e.target.value});
|
||||
// The input field's value was changed.
|
||||
this.setState({
|
||||
message: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const formSubmitClasses = classNames({
|
||||
"mx_StatusMessageContextMenu_submit": true,
|
||||
"mx_StatusMessageContextMenu_submitFaded": !this.state.message, // no message == faded
|
||||
});
|
||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||
|
||||
const form = <form className="mx_StatusMessageContextMenu_form" onSubmit={this._onSubmit} autoComplete="off">
|
||||
<input type="text" key="message" placeholder={_t("Set a new status...")} autoFocus={true}
|
||||
className="mx_StatusMessageContextMenu_message"
|
||||
value={this.state.message} onChange={this._onStatusChange} maxLength="60" />
|
||||
<AccessibleButton onClick={this._onSubmit} element="div" className={formSubmitClasses}>
|
||||
<img src="img/icons-checkmark.svg" width="22" height="22" />
|
||||
</AccessibleButton>
|
||||
let actionButton;
|
||||
if (this.comittedStatusMessage) {
|
||||
if (this.state.message === this.comittedStatusMessage) {
|
||||
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_clear"
|
||||
onClick={this._onClearClick}
|
||||
>
|
||||
<span>{_t("Clear status")}</span>
|
||||
</AccessibleButton>;
|
||||
} else {
|
||||
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_submit"
|
||||
onClick={this._onSubmit}
|
||||
>
|
||||
<span>{_t("Update status")}</span>
|
||||
</AccessibleButton>;
|
||||
}
|
||||
} else {
|
||||
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_submit"
|
||||
disabled={!this.state.message} onClick={this._onSubmit}
|
||||
>
|
||||
<span>{_t("Set status")}</span>
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
let spinner = null;
|
||||
if (this.state.waiting) {
|
||||
spinner = <Spinner w="24" h="24" />;
|
||||
}
|
||||
|
||||
const form = <form className="mx_StatusMessageContextMenu_form"
|
||||
autoComplete="off" onSubmit={this._onSubmit}
|
||||
>
|
||||
<input type="text" className="mx_StatusMessageContextMenu_message"
|
||||
key="message" placeholder={_t("Set a new status...")}
|
||||
autoFocus={true} maxLength="60" value={this.state.message}
|
||||
onChange={this._onStatusChange}
|
||||
/>
|
||||
<div className="mx_StatusMessageContextMenu_actionContainer">
|
||||
{actionButton}
|
||||
{spinner}
|
||||
</div>
|
||||
</form>;
|
||||
|
||||
const clearIcon = this.state.message ? "img/cancel-red.svg" : "img/cancel.svg";
|
||||
const clearButton = <AccessibleButton onClick={this._onClearClick} disabled={!this.state.message}
|
||||
className="mx_StatusMessageContextMenu_clear">
|
||||
<img src={clearIcon} alt={_t('Clear status')} width="12" height="12"
|
||||
className="mx_filterFlipColor mx_StatusMessageContextMenu_clearIcon" />
|
||||
<span>{_t("Clear status")}</span>
|
||||
</AccessibleButton>;
|
||||
|
||||
const menuClasses = classNames({
|
||||
"mx_StatusMessageContextMenu": true,
|
||||
"mx_StatusMessageContextMenu_hasStatus": this.state.message,
|
||||
});
|
||||
|
||||
return <div className={menuClasses}>
|
||||
return <div className="mx_StatusMessageContextMenu">
|
||||
{ form }
|
||||
<hr />
|
||||
{ clearButton }
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import dis from '../../../dispatcher';
|
|||
import TagOrderActions from '../../../actions/TagOrderActions';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
export default class TagTileContextMenu extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -35,7 +34,6 @@ export default class TagTileContextMenu extends React.Component {
|
|||
|
||||
this._onViewCommunityClick = this._onViewCommunityClick.bind(this);
|
||||
this._onRemoveClick = this._onRemoveClick.bind(this);
|
||||
this._onViewAsGridClick = this._onViewAsGridClick.bind(this);
|
||||
}
|
||||
|
||||
_onViewCommunityClick() {
|
||||
|
@ -55,28 +53,8 @@ export default class TagTileContextMenu extends React.Component {
|
|||
this.props.onFinished();
|
||||
}
|
||||
|
||||
_onViewAsGridClick() {
|
||||
dis.dispatch({
|
||||
action: 'group_grid_view',
|
||||
group_id: this.props.tag,
|
||||
});
|
||||
this.props.onFinished();
|
||||
}
|
||||
|
||||
render() {
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
let gridViewOption;
|
||||
if (SettingsStore.isFeatureEnabled("feature_gridview")) {
|
||||
gridViewOption = (<div className="mx_TagTileContextMenu_item" onClick={this._onViewAsGridClick} >
|
||||
<TintableSvg
|
||||
className="mx_TagTileContextMenu_item_icon"
|
||||
src="img/feather-icons/grid.svg"
|
||||
width="15"
|
||||
height="15"
|
||||
/>
|
||||
{ _t('View as Grid') }
|
||||
</div>);
|
||||
}
|
||||
return <div>
|
||||
<div className="mx_TagTileContextMenu_item" onClick={this._onViewCommunityClick} >
|
||||
<TintableSvg
|
||||
|
@ -87,7 +65,6 @@ export default class TagTileContextMenu extends React.Component {
|
|||
/>
|
||||
{ _t('View Community') }
|
||||
</div>
|
||||
{ gridViewOption }
|
||||
<hr className="mx_TagTileContextMenu_separator" />
|
||||
<div className="mx_TagTileContextMenu_item" onClick={this._onRemoveClick} >
|
||||
<img className="mx_TagTileContextMenu_item_icon" src="img/icon_context_delete.svg" width="15" height="15" />
|
||||
|
|
81
src/components/views/dialogs/AskInviteAnywayDialog.js
Normal file
81
src/components/views/dialogs/AskInviteAnywayDialog.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {SettingLevel} from "../../../settings/SettingsStore";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
export default React.createClass({
|
||||
propTypes: {
|
||||
unknownProfileUsers: PropTypes.array.isRequired, // [ {userId, errorText}... ]
|
||||
onInviteAnyways: PropTypes.func.isRequired,
|
||||
onGiveUp: PropTypes.func.isRequired,
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
_onInviteClicked: function() {
|
||||
this.props.onInviteAnyways();
|
||||
this.props.onFinished(true);
|
||||
},
|
||||
|
||||
_onInviteNeverWarnClicked: function() {
|
||||
SettingsStore.setValue("promptBeforeInviteUnknownUsers", null, SettingLevel.ACCOUNT, false);
|
||||
this.props.onInviteAnyways();
|
||||
this.props.onFinished(true);
|
||||
},
|
||||
|
||||
_onGiveUpClicked: function() {
|
||||
this.props.onGiveUp();
|
||||
this.props.onFinished(false);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
|
||||
const errorList = this.props.unknownProfileUsers
|
||||
.map(address => <li key={address.userId}>{address.userId}: {address.errorText}</li>);
|
||||
|
||||
return (
|
||||
<BaseDialog className='mx_RetryInvitesDialog'
|
||||
onFinished={this._onGiveUpClicked}
|
||||
title={_t('The following users may not exist')}
|
||||
contentId='mx_Dialog_content'
|
||||
>
|
||||
<div id='mx_Dialog_content'>
|
||||
<p>{_t("Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?")}</p>
|
||||
<ul>
|
||||
{ errorList }
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this._onGiveUpClicked}>
|
||||
{ _t('Close') }
|
||||
</button>
|
||||
<button onClick={this._onInviteNeverWarnClicked}>
|
||||
{ _t('Invite anyway and never warn me again') }
|
||||
</button>
|
||||
<button onClick={this._onInviteClicked} autoFocus="true">
|
||||
{ _t('Invite anyway') }
|
||||
</button>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -36,7 +36,7 @@ export default class ChangelogDialog extends React.Component {
|
|||
for (let i=0; i<REPOS.length; i++) {
|
||||
const oldVersion = version2[2*i];
|
||||
const newVersion = version[2*i];
|
||||
const url = `https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`;
|
||||
const url = `https://riot.im/github/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`;
|
||||
request(url, (err, response, body) => {
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
this.setState({ [REPOS[i]]: response.statusText });
|
||||
|
|
|
@ -78,6 +78,7 @@ export default class HeaderButtons extends React.Component {
|
|||
// till show_right_panel, just without the fromHeader flag
|
||||
// as that would hide the right panel again
|
||||
dis.dispatch(Object.assign({}, payload, {fromHeader: false}));
|
||||
|
||||
}
|
||||
this.setState({
|
||||
phase: payload.phase,
|
||||
|
|
|
@ -39,6 +39,7 @@ import Unread from '../../../Unread';
|
|||
import { findReadReceiptFromUserId } from '../../../utils/Receipt';
|
||||
import withMatrixClient from '../../../wrappers/withMatrixClient';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import MultiInviter from "../../../utils/MultiInviter";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
@ -49,7 +50,6 @@ module.exports = withMatrixClient(React.createClass({
|
|||
propTypes: {
|
||||
matrixClient: PropTypes.object.isRequired,
|
||||
member: PropTypes.object.isRequired,
|
||||
roomId: PropTypes.string,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -713,7 +713,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
}
|
||||
|
||||
if (!member || !member.membership || member.membership === 'leave') {
|
||||
const roomId = member && member.roomId ? member.roomId : this.props.roomId;
|
||||
const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId();
|
||||
const onInviteUserButton = async () => {
|
||||
try {
|
||||
// We use a MultiInviter to re-use the invite logic, even though
|
||||
|
|
|
@ -21,10 +21,8 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
const sdk = require('../../../index');
|
||||
const dis = require('../../../dispatcher');
|
||||
const Modal = require("../../../Modal");
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
@ -42,7 +40,46 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {};
|
||||
return {
|
||||
statusMessage: this.getStatusMessage(),
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
return;
|
||||
}
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
|
||||
},
|
||||
|
||||
componentWillUmount() {
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.removeListener(
|
||||
"User._unstable_statusMessage",
|
||||
this._onStatusMessageCommitted,
|
||||
);
|
||||
},
|
||||
|
||||
getStatusMessage() {
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return "";
|
||||
}
|
||||
return user._unstable_statusMessage;
|
||||
},
|
||||
|
||||
_onStatusMessageCommitted() {
|
||||
// The `User` object has observed a status message change.
|
||||
this.setState({
|
||||
statusMessage: this.getStatusMessage(),
|
||||
});
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
|
@ -74,22 +111,23 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
getPowerLabel: function() {
|
||||
return _t("%(userName)s (power %(powerLevelNumber)s)", {userName: this.props.member.userId, powerLevelNumber: this.props.member.powerLevel});
|
||||
return _t("%(userName)s (power %(powerLevelNumber)s)", {
|
||||
userName: this.props.member.userId,
|
||||
powerLevelNumber: this.props.member.powerLevel,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
const EntityTile = sdk.getComponent('rooms.EntityTile');
|
||||
|
||||
const member = this.props.member;
|
||||
const name = this._getDisplayName();
|
||||
const active = -1;
|
||||
const presenceState = member.user ? member.user.presence : null;
|
||||
|
||||
let statusMessage = null;
|
||||
if (member.user && SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
statusMessage = member.user._unstable_statusMessage;
|
||||
statusMessage = this.state.statusMessage;
|
||||
}
|
||||
|
||||
const av = (
|
||||
|
|
|
@ -22,6 +22,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
|
|||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
import Stickerpicker from './Stickerpicker';
|
||||
import { makeRoomPermalink } from '../../../matrix-to';
|
||||
|
@ -62,7 +63,7 @@ export default class MessageComposer extends React.Component {
|
|||
isRichTextEnabled: SettingsStore.getValue('MessageComposerInput.isRichTextEnabled'),
|
||||
},
|
||||
showFormatting: SettingsStore.getValue('MessageComposer.showFormatting'),
|
||||
isQuoting: Boolean(this.props.roomViewStore.getQuotingEvent()),
|
||||
isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
|
||||
tombstone: this._getRoomTombstone(),
|
||||
};
|
||||
}
|
||||
|
@ -74,7 +75,7 @@ export default class MessageComposer extends React.Component {
|
|||
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
|
||||
MatrixClientPeg.get().on("event", this.onEvent);
|
||||
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
|
||||
this._roomStoreToken = this.props.roomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this._waitForOwnMember();
|
||||
}
|
||||
|
||||
|
@ -123,14 +124,14 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
|
||||
_onRoomViewStoreUpdate() {
|
||||
const isQuoting = Boolean(this.props.roomViewStore.getQuotingEvent());
|
||||
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
||||
if (this.state.isQuoting === isQuoting) return;
|
||||
this.setState({ isQuoting });
|
||||
}
|
||||
|
||||
onUploadClick(ev) {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
this.props.roomViewStore.getDispatcher().dispatch({action: 'require_registration'});
|
||||
dis.dispatch({action: 'require_registration'});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -164,7 +165,7 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const isQuoting = Boolean(this.props.roomViewStore.getQuotingEvent());
|
||||
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
||||
let replyToWarning = null;
|
||||
if (isQuoting) {
|
||||
replyToWarning = <p>{
|
||||
|
@ -228,7 +229,7 @@ export default class MessageComposer extends React.Component {
|
|||
if (!call) {
|
||||
return;
|
||||
}
|
||||
this.props.roomViewStore.getDispatcher().dispatch({
|
||||
dis.dispatch({
|
||||
action: 'hangup',
|
||||
// hangup the call for this room, which may not be the room in props
|
||||
// (e.g. conferences which will hangup the 1:1 room instead)
|
||||
|
@ -237,7 +238,7 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
|
||||
onCallClick(ev) {
|
||||
this.props.roomViewStore.getDispatcher().dispatch({
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: ev.shiftKey ? "screensharing" : "video",
|
||||
room_id: this.props.room.roomId,
|
||||
|
@ -245,7 +246,7 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
|
||||
onVoiceCallClick(ev) {
|
||||
this.props.roomViewStore.getDispatcher().dispatch({
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: "voice",
|
||||
room_id: this.props.room.roomId,
|
||||
|
@ -287,8 +288,7 @@ export default class MessageComposer extends React.Component {
|
|||
const createEvent = replacementRoom.currentState.getStateEvents('m.room.create', '');
|
||||
if (createEvent && createEvent.getId()) createEventId = createEvent.getId();
|
||||
}
|
||||
|
||||
this.props.roomViewStore.getDispatcher().dispatch({
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
highlighted: true,
|
||||
event_id: createEventId,
|
||||
|
@ -432,10 +432,8 @@ export default class MessageComposer extends React.Component {
|
|||
|
||||
controls.push(
|
||||
<MessageComposerInput
|
||||
roomViewStore={this.props.roomViewStore}
|
||||
ref={(c) => this.messageComposerInput = c}
|
||||
key="controls_input"
|
||||
isGrid={this.props.isGrid}
|
||||
onResize={this.props.onResize}
|
||||
room={this.props.room}
|
||||
placeholder={placeholderText}
|
||||
|
@ -542,6 +540,5 @@ MessageComposer.propTypes = {
|
|||
uploadAllowed: PropTypes.func.isRequired,
|
||||
|
||||
// string representing the current room app drawer state
|
||||
showApps: PropTypes.bool,
|
||||
roomViewStore: PropTypes.object.isRequired,
|
||||
showApps: PropTypes.bool
|
||||
};
|
||||
|
|
|
@ -38,6 +38,8 @@ import sdk from '../../../index';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import Analytics from '../../../Analytics';
|
||||
|
||||
import dis from '../../../dispatcher';
|
||||
|
||||
import * as RichText from '../../../RichText';
|
||||
import * as HtmlUtils from '../../../HtmlUtils';
|
||||
import Autocomplete from './Autocomplete';
|
||||
|
@ -55,6 +57,7 @@ import {
|
|||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
import {makeUserPermalink} from "../../../matrix-to";
|
||||
import ReplyPreview from "./ReplyPreview";
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import ReplyThread from "../elements/ReplyThread";
|
||||
import {ContentHelpers} from 'matrix-js-sdk';
|
||||
|
||||
|
@ -108,6 +111,15 @@ const SLATE_SCHEMA = {
|
|||
},
|
||||
};
|
||||
|
||||
function onSendMessageFailed(err, room) {
|
||||
// XXX: temporary logging to try to diagnose
|
||||
// https://github.com/vector-im/riot-web/issues/3148
|
||||
console.log('MessageComposer got send failure: ' + err.name + '('+err+')');
|
||||
dis.dispatch({
|
||||
action: 'message_send_failed',
|
||||
});
|
||||
}
|
||||
|
||||
function rangeEquals(a: Range, b: Range): boolean {
|
||||
return (a.anchor.key === b.anchor.key
|
||||
&& a.anchor.offset === b.anchorOffset
|
||||
|
@ -117,18 +129,6 @@ function rangeEquals(a: Range, b: Range): boolean {
|
|||
&& a.isBackward === b.isBackward);
|
||||
}
|
||||
|
||||
class NoopHistoryManager {
|
||||
getItem() {}
|
||||
save() {}
|
||||
|
||||
get currentIndex() { return 0; }
|
||||
set currentIndex(_) {}
|
||||
|
||||
get history() { return []; }
|
||||
set history(_) {}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* The textInput part of the MessageComposer
|
||||
*/
|
||||
|
@ -144,7 +144,6 @@ export default class MessageComposerInput extends React.Component {
|
|||
onFilesPasted: PropTypes.func,
|
||||
|
||||
onInputStateChanged: PropTypes.func,
|
||||
roomViewStore: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
client: MatrixClient;
|
||||
|
@ -339,31 +338,18 @@ export default class MessageComposerInput extends React.Component {
|
|||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.dispatcherRef = this.props.roomViewStore.getDispatcher().register(this.onAction);
|
||||
if (this.props.isGrid) {
|
||||
this.historyManager = new NoopHistoryManager();
|
||||
} else {
|
||||
this.historyManager = new ComposerHistoryManager(this.props.room.roomId, 'mx_slate_composer_history_');
|
||||
}
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this.historyManager = new ComposerHistoryManager(this.props.room.roomId, 'mx_slate_composer_history_');
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.roomViewStore.getDispatcher().unregister(this.dispatcherRef);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
_collectEditor = (e) => {
|
||||
this._editor = e;
|
||||
}
|
||||
|
||||
onSendMessageFailed = (err, room) => {
|
||||
// XXX: temporary logging to try to diagnose
|
||||
// https://github.com/vector-im/riot-web/issues/3148
|
||||
console.log('MessageComposer got send failure: ' + err.name + '('+err+')');
|
||||
this.props.roomViewStore.getDispatcher().dispatch({
|
||||
action: 'message_send_failed',
|
||||
});
|
||||
}
|
||||
|
||||
onAction = (payload) => {
|
||||
const editorState = this.state.editorState;
|
||||
|
||||
|
@ -1129,7 +1115,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
return true;
|
||||
}
|
||||
|
||||
const replyingToEv = this.props.roomViewStore.getQuotingEvent();
|
||||
const replyingToEv = RoomViewStore.getQuotingEvent();
|
||||
const mustSendHTML = Boolean(replyingToEv);
|
||||
|
||||
if (this.state.isRichTextEnabled) {
|
||||
|
@ -1217,18 +1203,18 @@ export default class MessageComposerInput extends React.Component {
|
|||
|
||||
// Clear reply_to_event as we put the message into the queue
|
||||
// if the send fails, retry will handle resending.
|
||||
this.props.roomViewStore.getDispatcher().dispatch({
|
||||
dis.dispatch({
|
||||
action: 'reply_to_event',
|
||||
event: null,
|
||||
});
|
||||
}
|
||||
|
||||
this.client.sendMessage(this.props.room.roomId, content).then((res) => {
|
||||
this.props.roomViewStore.getDispatcher().dispatch({
|
||||
dis.dispatch({
|
||||
action: 'message_sent',
|
||||
});
|
||||
}).catch((e) => {
|
||||
this.onSendMessageFailed(e, this.props.room);
|
||||
onSendMessageFailed(e, this.props.room);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
|
@ -1599,7 +1585,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
return (
|
||||
<div className="mx_MessageComposer_input_wrapper" onClick={this.focusComposer}>
|
||||
<div className="mx_MessageComposer_autocomplete_wrapper">
|
||||
<ReplyPreview roomViewStore={this.props.roomViewStore} />
|
||||
<ReplyPreview />
|
||||
<Autocomplete
|
||||
ref={(e) => this.autocomplete = e}
|
||||
room={this.props.room}
|
||||
|
|
|
@ -18,6 +18,7 @@ import React from 'react';
|
|||
import dis from '../../../dispatcher';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
function cancelQuoting() {
|
||||
|
@ -37,7 +38,7 @@ export default class ReplyPreview extends React.Component {
|
|||
|
||||
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
|
||||
|
||||
this._roomStoreToken = this.props.roomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this._onRoomViewStoreUpdate();
|
||||
}
|
||||
|
||||
|
@ -49,7 +50,7 @@ export default class ReplyPreview extends React.Component {
|
|||
}
|
||||
|
||||
_onRoomViewStoreUpdate() {
|
||||
const event = this.props.roomViewStore.getQuotingEvent();
|
||||
const event = RoomViewStore.getQuotingEvent();
|
||||
if (this.state.event !== event) {
|
||||
this.setState({ event });
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import { _t } from '../../../languageHandler';
|
|||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import Modal from "../../../Modal";
|
||||
import RateLimitedFunc from '../../../ratelimitedfunc';
|
||||
import dis from '../../../dispatcher';
|
||||
|
||||
import * as linkify from 'linkifyjs';
|
||||
import linkifyElement from 'linkifyjs/element';
|
||||
|
@ -153,14 +152,6 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
onToggleRightPanelClick: function(ev) {
|
||||
if (this.props.collapsedRhs) {
|
||||
dis.dispatch({action: "show_right_panel"});
|
||||
} else {
|
||||
dis.dispatch({action: "hide_right_panel"});
|
||||
}
|
||||
},
|
||||
|
||||
_hasUnreadPins: function() {
|
||||
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
|
||||
if (!currentPinEvent) return false;
|
||||
|
@ -418,17 +409,6 @@ module.exports = React.createClass({
|
|||
</div>;
|
||||
}
|
||||
|
||||
let toggleRightPanelButton;
|
||||
if (this.props.isGrid) {
|
||||
toggleRightPanelButton =
|
||||
<AccessibleButton
|
||||
className="mx_RoomHeader_button"
|
||||
onClick={this.onToggleRightPanelClick}
|
||||
title={_t('Toggle right panel')}>
|
||||
<TintableSvg src="img/feather-icons/toggle-right-panel.svg" width="20" height="20" />
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"mx_RoomHeader light-panel " + (this.props.editing ? "mx_RoomHeader_editing" : "")}>
|
||||
<div className="mx_RoomHeader_wrapper">
|
||||
|
@ -439,8 +419,7 @@ module.exports = React.createClass({
|
|||
{ saveButton }
|
||||
{ cancelButton }
|
||||
{ rightRow }
|
||||
{ !this.props.isGrid ? <RoomHeaderButtons collapsedRhs={this.props.collapsedRhs} /> : undefined }
|
||||
{ toggleRightPanelButton }
|
||||
<RoomHeaderButtons collapsedRhs={this.props.collapsedRhs} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -36,7 +36,7 @@ import GroupStore from '../../../stores/GroupStore';
|
|||
import RoomSubList from '../../structures/RoomSubList';
|
||||
import ResizeHandle from '../elements/ResizeHandle';
|
||||
|
||||
import {Resizer, RoomDistributor, RoomSizer} from '../../../resizer'
|
||||
import {Resizer, RoomSubListDistributor} from '../../../resizer'
|
||||
const HIDE_CONFERENCE_CHANS = true;
|
||||
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
|
||||
|
||||
|
@ -153,7 +153,11 @@ module.exports = React.createClass({
|
|||
if (typeof newSize === "string") {
|
||||
newSize = Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
this.subListSizes[id] = newSize;
|
||||
if (newSize === null) {
|
||||
delete this.subListSizes[id];
|
||||
} else {
|
||||
this.subListSizes[id] = newSize;
|
||||
}
|
||||
window.localStorage.setItem("mx_roomlist_sizes", JSON.stringify(this.subListSizes));
|
||||
// update overflow indicators
|
||||
this._checkSubListsOverflow();
|
||||
|
@ -164,7 +168,7 @@ module.exports = React.createClass({
|
|||
const cfg = {
|
||||
onResized: this._onSubListResize,
|
||||
};
|
||||
this.resizer = new Resizer(this.resizeContainer, RoomDistributor, cfg, RoomSizer);
|
||||
this.resizer = new Resizer(this.resizeContainer, RoomSubListDistributor, cfg);
|
||||
this.resizer.setClassNames({
|
||||
handle: "mx_ResizeHandle",
|
||||
vertical: "mx_ResizeHandle_vertical",
|
||||
|
@ -724,4 +728,4 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,6 +29,7 @@ import * as RoomNotifs from '../../../RoomNotifs';
|
|||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import ActiveRoomObserver from '../../../ActiveRoomObserver';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
@ -61,7 +62,8 @@ module.exports = React.createClass({
|
|||
roomName: this.props.room.name,
|
||||
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
||||
notificationCount: this.props.room.getUnreadNotificationCount(),
|
||||
selected: this.props.room.roomId === ActiveRoomObserver.getActiveRoomId(),
|
||||
selected: this.props.room.roomId === RoomViewStore.getRoomId(),
|
||||
statusMessage: this._getStatusMessage(),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -79,6 +81,33 @@ module.exports = React.createClass({
|
|||
return Boolean(dmRooms);
|
||||
},
|
||||
|
||||
_shouldShowStatusMessage() {
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
return false;
|
||||
}
|
||||
const isInvite = this.props.room.getMyMembership() === "invite";
|
||||
const isJoined = this.props.room.getMyMembership() === "join";
|
||||
const looksLikeDm = this.props.room.getInvitedAndJoinedMemberCount() === 2;
|
||||
return !isInvite && isJoined && looksLikeDm;
|
||||
},
|
||||
|
||||
_getStatusMessageUser() {
|
||||
const selfId = MatrixClientPeg.get().getUserId();
|
||||
const otherMember = this.props.room.currentState.getMembersExcept([selfId])[0];
|
||||
if (!otherMember) {
|
||||
return null;
|
||||
}
|
||||
return otherMember.user;
|
||||
},
|
||||
|
||||
_getStatusMessage() {
|
||||
const statusUser = this._getStatusMessageUser();
|
||||
if (!statusUser) {
|
||||
return "";
|
||||
}
|
||||
return statusUser._unstable_statusMessage;
|
||||
},
|
||||
|
||||
onRoomTimeline: function(ev, room) {
|
||||
if (room !== this.props.room) return;
|
||||
this.setState({
|
||||
|
@ -112,13 +141,19 @@ module.exports = React.createClass({
|
|||
this.setState({
|
||||
notificationCount: this.props.room.getUnreadNotificationCount(),
|
||||
});
|
||||
break;
|
||||
break;
|
||||
// RoomTiles are one of the few components that may show custom status and
|
||||
// also remain on screen while in Settings toggling the feature. This ensures
|
||||
// you can clearly see the status hide and show when toggling the feature.
|
||||
case 'feature_custom_status_changed':
|
||||
this.forceUpdate();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_onActiveRoomChange: function(activeRoomId) {
|
||||
_onActiveRoomChange: function() {
|
||||
this.setState({
|
||||
selected: this.props.room.roomId === activeRoomId,
|
||||
selected: this.props.room.roomId === RoomViewStore.getRoomId(),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -128,6 +163,16 @@ module.exports = React.createClass({
|
|||
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
||||
ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
|
||||
if (this._shouldShowStatusMessage()) {
|
||||
const statusUser = this._getStatusMessageUser();
|
||||
if (statusUser) {
|
||||
statusUser.on(
|
||||
"User._unstable_statusMessage",
|
||||
this._onStatusMessageCommitted,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
@ -139,6 +184,16 @@ module.exports = React.createClass({
|
|||
}
|
||||
ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
|
||||
if (this._shouldShowStatusMessage()) {
|
||||
const statusUser = this._getStatusMessageUser();
|
||||
if (statusUser) {
|
||||
statusUser.removeListener(
|
||||
"User._unstable_statusMessage",
|
||||
this._onStatusMessageCommitted,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(props) {
|
||||
|
@ -166,6 +221,13 @@ module.exports = React.createClass({
|
|||
return false;
|
||||
},
|
||||
|
||||
_onStatusMessageCommitted() {
|
||||
// The status message `User` object has observed a message change.
|
||||
this.setState({
|
||||
statusMessage: this._getStatusMessage(),
|
||||
});
|
||||
},
|
||||
|
||||
onClick: function(ev) {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(this.props.room.roomId, ev);
|
||||
|
@ -251,15 +313,9 @@ module.exports = React.createClass({
|
|||
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
|
||||
const badges = notifBadges || mentionBadges;
|
||||
|
||||
const isJoined = this.props.room.getMyMembership() === "join";
|
||||
const looksLikeDm = this.props.room.getInvitedAndJoinedMemberCount() === 2;
|
||||
let subtext = null;
|
||||
if (!isInvite && isJoined && looksLikeDm && SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
const selfId = MatrixClientPeg.get().getUserId();
|
||||
const otherMember = this.props.room.currentState.getMembersExcept([selfId])[0];
|
||||
if (otherMember && otherMember.user && otherMember.user._unstable_statusMessage) {
|
||||
subtext = otherMember.user._unstable_statusMessage;
|
||||
}
|
||||
if (this._shouldShowStatusMessage()) {
|
||||
subtext = this.state.statusMessage;
|
||||
}
|
||||
|
||||
const classes = classNames({
|
||||
|
|
|
@ -33,11 +33,11 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onThisRoomClick: function() {
|
||||
this.setState({ scope: 'Room' });
|
||||
this.setState({ scope: 'Room' }, () => this._searchIfQuery());
|
||||
},
|
||||
|
||||
onAllRoomsClick: function() {
|
||||
this.setState({ scope: 'All' });
|
||||
this.setState({ scope: 'All' }, () => this._searchIfQuery());
|
||||
},
|
||||
|
||||
onSearchChange: function(e) {
|
||||
|
@ -49,6 +49,12 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_searchIfQuery: function() {
|
||||
if (this.refs.search_term.value) {
|
||||
this.onSearch();
|
||||
}
|
||||
},
|
||||
|
||||
onSearch: function() {
|
||||
this.props.onSearch(this.refs.search_term.value, this.state.scope);
|
||||
},
|
||||
|
@ -60,11 +66,13 @@ module.exports = React.createClass({
|
|||
|
||||
return (
|
||||
<div className="mx_SearchBar">
|
||||
<input ref="search_term" className="mx_SearchBar_input" type="text" autoFocus={true} placeholder={_t("Search…")} onKeyDown={this.onSearchChange} />
|
||||
<AccessibleButton className={ searchButtonClasses } onClick={this.onSearch}><img src="img/search-button.svg" width="37" height="37" alt={_t("Search")} /></AccessibleButton>
|
||||
<AccessibleButton className={ thisRoomClasses } onClick={this.onThisRoomClick}>{_t("This Room")}</AccessibleButton>
|
||||
<AccessibleButton className={ allRoomsClasses } onClick={this.onAllRoomsClick}>{_t("All Rooms")}</AccessibleButton>
|
||||
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" /></AccessibleButton>
|
||||
<div className="mx_SearchBar_input mx_textinput">
|
||||
<input ref="search_term" type="text" autoFocus={true} placeholder={_t("Search…")} onKeyDown={this.onSearchChange} />
|
||||
<AccessibleButton className={ searchButtonClasses } onClick={this.onSearch}></AccessibleButton>
|
||||
</div>
|
||||
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick}></AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -186,18 +186,23 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
}
|
||||
|
||||
let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => {
|
||||
const deviceName = sig.device.getDisplayName() || sig.device.deviceId;
|
||||
const deviceName = sig.device ? (sig.device.getDisplayName() || sig.device.deviceId) : null;
|
||||
const validity = sub =>
|
||||
<span className={sig.valid ? 'mx_KeyBackupPanel_sigValid' : 'mx_KeyBackupPanel_sigInvalid'}>
|
||||
{sub}
|
||||
</span>;
|
||||
const verify = sub =>
|
||||
<span className={sig.device.isVerified() ? 'mx_KeyBackupPanel_deviceVerified' : 'mx_KeyBackupPanel_deviceNotVerified'}>
|
||||
<span className={sig.device && sig.device.isVerified() ? 'mx_KeyBackupPanel_deviceVerified' : 'mx_KeyBackupPanel_deviceNotVerified'}>
|
||||
{sub}
|
||||
</span>;
|
||||
const device = sub => <span className="mx_KeyBackupPanel_deviceName">{deviceName}</span>;
|
||||
let sigStatus;
|
||||
if (sig.device.getFingerprint() === MatrixClientPeg.get().getDeviceEd25519Key()) {
|
||||
if (!sig.device) {
|
||||
sigStatus = _t(
|
||||
"Backup has a signature from <verify>unknown</verify> device with ID %(deviceId)s.",
|
||||
{ deviceId: sig.deviceId }, { verify },
|
||||
);
|
||||
} else if (sig.device.getFingerprint() === MatrixClientPeg.get().getDeviceEd25519Key()) {
|
||||
sigStatus = _t(
|
||||
"Backup has a <validity>valid</validity> signature from this device",
|
||||
{}, { validity },
|
||||
|
@ -229,7 +234,7 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
}
|
||||
|
||||
let verifyButton;
|
||||
if (!sig.device.isVerified()) {
|
||||
if (!sig.device || !sig.device.isVerified()) {
|
||||
verifyButton = <div><br /><AccessibleButton className="mx_UserSettings_button"
|
||||
onClick={this._verifyDevice} data-sigindex={i}>
|
||||
{ _t("Verify...") }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue