Merge branch 'develop' into matthew/fix_logging
This commit is contained in:
commit
9f455fae4b
136 changed files with 1483 additions and 775 deletions
|
@ -19,7 +19,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import Promise from 'bluebird';
|
||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
|
@ -637,7 +636,7 @@ export default createReactClass({
|
|||
title: _t('Error'),
|
||||
description: _t('Failed to upload image'),
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
_onJoinableChange: function(ev) {
|
||||
|
@ -676,7 +675,7 @@ export default createReactClass({
|
|||
this.setState({
|
||||
avatarChanged: false,
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
_saveGroup: async function() {
|
||||
|
|
|
@ -121,7 +121,7 @@ export default createReactClass({
|
|||
this.setState({
|
||||
errorText: msg,
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
|
||||
this._intervalId = null;
|
||||
if (this.props.poll) {
|
||||
|
|
|
@ -525,6 +525,7 @@ const LoggedInView = createReactClass({
|
|||
const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage');
|
||||
const GroupView = sdk.getComponent('structures.GroupView');
|
||||
const MyGroups = sdk.getComponent('structures.MyGroups');
|
||||
const ToastContainer = sdk.getComponent('structures.ToastContainer');
|
||||
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
||||
const CookieBar = sdk.getComponent('globals.CookieBar');
|
||||
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
||||
|
@ -628,6 +629,7 @@ const LoggedInView = createReactClass({
|
|||
return (
|
||||
<div onPaste={this._onPaste} onKeyDown={this._onReactKeyDown} className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}>
|
||||
{ topBar }
|
||||
<ToastContainer />
|
||||
<DragDropContext onDragEnd={this._onDragEnd}>
|
||||
<div ref={this._setResizeContainerRef} className={bodyClasses}>
|
||||
<LeftPanel
|
||||
|
|
|
@ -17,8 +17,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -62,10 +60,7 @@ import { countRoomsWithNotif } from '../../RoomNotifs';
|
|||
import { ThemeWatcher } from "../../theme";
|
||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||
import { defer } from "../../utils/promise";
|
||||
|
||||
// Disable warnings for now: we use deprecated bluebird functions
|
||||
// and need to migrate, but they spam the console with warnings.
|
||||
Promise.config({warnings: false});
|
||||
import KeyVerificationStateObserver from '../../utils/KeyVerificationStateObserver';
|
||||
|
||||
/** constants for MatrixChat.state.view */
|
||||
const VIEWS = {
|
||||
|
@ -545,7 +540,7 @@ export default createReactClass({
|
|||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||
|
||||
MatrixClientPeg.get().leave(payload.room_id).done(() => {
|
||||
MatrixClientPeg.get().leave(payload.room_id).then(() => {
|
||||
modal.close();
|
||||
if (this.state.currentRoomId === payload.room_id) {
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
|
@ -863,7 +858,7 @@ export default createReactClass({
|
|||
waitFor = this.firstSyncPromise.promise;
|
||||
}
|
||||
|
||||
waitFor.done(() => {
|
||||
waitFor.then(() => {
|
||||
let presentedId = roomInfo.room_alias || roomInfo.room_id;
|
||||
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
|
||||
if (room) {
|
||||
|
@ -980,7 +975,7 @@ export default createReactClass({
|
|||
|
||||
const [shouldCreate, createOpts] = await modal.finished;
|
||||
if (shouldCreate) {
|
||||
createRoom({createOpts}).done();
|
||||
createRoom({createOpts});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1270,7 +1265,6 @@ export default createReactClass({
|
|||
this.firstSyncComplete = false;
|
||||
this.firstSyncPromise = defer();
|
||||
const cli = MatrixClientPeg.get();
|
||||
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
|
||||
|
||||
// Allow the JS SDK to reap timeline events. This reduces the amount of
|
||||
// memory consumed as the JS SDK stores multiple distinct copies of room
|
||||
|
@ -1469,12 +1463,35 @@ export default createReactClass({
|
|||
}
|
||||
});
|
||||
|
||||
cli.on("crypto.verification.start", (verifier) => {
|
||||
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
|
||||
verifier,
|
||||
});
|
||||
});
|
||||
if (SettingsStore.isFeatureEnabled("feature_dm_verification")) {
|
||||
cli.on("crypto.verification.request", request => {
|
||||
let requestObserver;
|
||||
if (request.event.getRoomId()) {
|
||||
requestObserver = new KeyVerificationStateObserver(
|
||||
request.event, MatrixClientPeg.get());
|
||||
}
|
||||
|
||||
if (!requestObserver || requestObserver.pending) {
|
||||
dis.dispatch({
|
||||
action: "show_toast",
|
||||
toast: {
|
||||
key: request.event.getId(),
|
||||
title: _t("Verification Request"),
|
||||
icon: "verification",
|
||||
props: {request, requestObserver},
|
||||
component: sdk.getComponent("toasts.VerificationRequestToast"),
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cli.on("crypto.verification.start", (verifier) => {
|
||||
const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog");
|
||||
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
|
||||
verifier,
|
||||
});
|
||||
});
|
||||
}
|
||||
// Fire the tinter right on startup to ensure the default theme is applied
|
||||
// A later sync can/will correct the tint to be the right value for the user
|
||||
const colorScheme = SettingsStore.getValue("roomColor");
|
||||
|
@ -1745,7 +1762,7 @@ export default createReactClass({
|
|||
return;
|
||||
}
|
||||
|
||||
cli.sendEvent(roomId, event.getType(), event.getContent()).done(() => {
|
||||
cli.sendEvent(roomId, event.getType(), event.getContent()).then(() => {
|
||||
dis.dispatch({action: 'message_sent'});
|
||||
}, (err) => {
|
||||
dis.dispatch({action: 'message_send_failed'});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -18,7 +19,6 @@ limitations under the License.
|
|||
/* global Velocity */
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
@ -37,10 +37,8 @@ const isMembershipChange = (e) => e.getType() === 'm.room.member' || e.getType()
|
|||
|
||||
/* (almost) stateless UI component which builds the event tiles in the room timeline.
|
||||
*/
|
||||
module.exports = createReactClass({
|
||||
displayName: 'MessagePanel',
|
||||
|
||||
propTypes: {
|
||||
export default class MessagePanel extends React.Component {
|
||||
static propTypes = {
|
||||
// true to give the component a 'display: none' style.
|
||||
hidden: PropTypes.bool,
|
||||
|
||||
|
@ -109,9 +107,10 @@ module.exports = createReactClass({
|
|||
|
||||
// whether to show reactions for an event
|
||||
showReactions: PropTypes.bool,
|
||||
},
|
||||
};
|
||||
|
||||
componentWillMount: function() {
|
||||
constructor() {
|
||||
super();
|
||||
// the event after which we put a visible unread marker on the last
|
||||
// render cycle; null if readMarkerVisible was false or the RM was
|
||||
// suppressed (eg because it was at the end of the timeline)
|
||||
|
@ -167,38 +166,42 @@ module.exports = createReactClass({
|
|||
this._showHiddenEventsInTimeline =
|
||||
SettingsStore.getValue("showHiddenEventsInTimeline");
|
||||
|
||||
this._isMounted = true;
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this._isMounted = false;
|
||||
},
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
/* get the DOM node representing the given event */
|
||||
getNodeForEventId: function(eventId) {
|
||||
getNodeForEventId(eventId) {
|
||||
if (!this.eventNodes) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.eventNodes[eventId];
|
||||
},
|
||||
}
|
||||
|
||||
/* return true if the content is fully scrolled down right now; else false.
|
||||
*/
|
||||
isAtBottom: function() {
|
||||
isAtBottom() {
|
||||
return this.refs.scrollPanel
|
||||
&& this.refs.scrollPanel.isAtBottom();
|
||||
},
|
||||
}
|
||||
|
||||
/* get the current scroll state. See ScrollPanel.getScrollState for
|
||||
* details.
|
||||
*
|
||||
* returns null if we are not mounted.
|
||||
*/
|
||||
getScrollState: function() {
|
||||
getScrollState() {
|
||||
if (!this.refs.scrollPanel) { return null; }
|
||||
return this.refs.scrollPanel.getScrollState();
|
||||
},
|
||||
}
|
||||
|
||||
// returns one of:
|
||||
//
|
||||
|
@ -206,7 +209,7 @@ module.exports = createReactClass({
|
|||
// -1: read marker is above the window
|
||||
// 0: read marker is within the window
|
||||
// +1: read marker is below the window
|
||||
getReadMarkerPosition: function() {
|
||||
getReadMarkerPosition() {
|
||||
const readMarker = this.refs.readMarkerNode;
|
||||
const messageWrapper = this.refs.scrollPanel;
|
||||
|
||||
|
@ -226,45 +229,45 @@ module.exports = createReactClass({
|
|||
} else {
|
||||
return 1;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/* jump to the top of the content.
|
||||
*/
|
||||
scrollToTop: function() {
|
||||
scrollToTop() {
|
||||
if (this.refs.scrollPanel) {
|
||||
this.refs.scrollPanel.scrollToTop();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/* jump to the bottom of the content.
|
||||
*/
|
||||
scrollToBottom: function() {
|
||||
scrollToBottom() {
|
||||
if (this.refs.scrollPanel) {
|
||||
this.refs.scrollPanel.scrollToBottom();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Page up/down.
|
||||
*
|
||||
* @param {number} mult: -1 to page up, +1 to page down
|
||||
*/
|
||||
scrollRelative: function(mult) {
|
||||
scrollRelative(mult) {
|
||||
if (this.refs.scrollPanel) {
|
||||
this.refs.scrollPanel.scrollRelative(mult);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll up/down in response to a scroll key
|
||||
*
|
||||
* @param {KeyboardEvent} ev: the keyboard event to handle
|
||||
*/
|
||||
handleScrollKey: function(ev) {
|
||||
handleScrollKey(ev) {
|
||||
if (this.refs.scrollPanel) {
|
||||
this.refs.scrollPanel.handleScrollKey(ev);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/* jump to the given event id.
|
||||
*
|
||||
|
@ -276,33 +279,33 @@ module.exports = createReactClass({
|
|||
* node (specifically, the bottom of it) will be positioned. If omitted, it
|
||||
* defaults to 0.
|
||||
*/
|
||||
scrollToEvent: function(eventId, pixelOffset, offsetBase) {
|
||||
scrollToEvent(eventId, pixelOffset, offsetBase) {
|
||||
if (this.refs.scrollPanel) {
|
||||
this.refs.scrollPanel.scrollToToken(eventId, pixelOffset, offsetBase);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
scrollToEventIfNeeded: function(eventId) {
|
||||
scrollToEventIfNeeded(eventId) {
|
||||
const node = this.eventNodes[eventId];
|
||||
if (node) {
|
||||
node.scrollIntoView({block: "nearest", behavior: "instant"});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/* check the scroll state and send out pagination requests if necessary.
|
||||
*/
|
||||
checkFillState: function() {
|
||||
checkFillState() {
|
||||
if (this.refs.scrollPanel) {
|
||||
this.refs.scrollPanel.checkFillState();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_isUnmounting: function() {
|
||||
_isUnmounting() {
|
||||
return !this._isMounted;
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: Implement granular (per-room) hide options
|
||||
_shouldShowEvent: function(mxEv) {
|
||||
_shouldShowEvent(mxEv) {
|
||||
if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) {
|
||||
return false; // ignored = no show (only happens if the ignore happens after an event was received)
|
||||
}
|
||||
|
@ -320,9 +323,9 @@ module.exports = createReactClass({
|
|||
if (this.props.highlightedEventId === mxEv.getId()) return true;
|
||||
|
||||
return !shouldHideEvent(mxEv);
|
||||
},
|
||||
}
|
||||
|
||||
_getEventTiles: function() {
|
||||
_getEventTiles() {
|
||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
||||
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
||||
|
@ -411,6 +414,12 @@ module.exports = createReactClass({
|
|||
readMarkerInSummary = true;
|
||||
}
|
||||
|
||||
// If this m.room.create event should be shown (room upgrade) then show it before the summary
|
||||
if (this._shouldShowEvent(mxEv)) {
|
||||
// pass in the mxEv as prevEvent as well so no extra DateSeparator is rendered
|
||||
ret.push(...this._getTilesForEvent(mxEv, mxEv, false));
|
||||
}
|
||||
|
||||
const summarisedEvents = []; // Don't add m.room.create here as we don't want it inside the summary
|
||||
for (;i + 1 < this.props.events.length; i++) {
|
||||
const collapsedMxEv = this.props.events[i + 1];
|
||||
|
@ -596,9 +605,9 @@ module.exports = createReactClass({
|
|||
|
||||
this.currentReadMarkerEventId = readMarkerVisible ? this.props.readMarkerEventId : null;
|
||||
return ret;
|
||||
},
|
||||
}
|
||||
|
||||
_getTilesForEvent: function(prevEvent, mxEv, last) {
|
||||
_getTilesForEvent(prevEvent, mxEv, last) {
|
||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
const ret = [];
|
||||
|
@ -691,20 +700,20 @@ module.exports = createReactClass({
|
|||
);
|
||||
|
||||
return ret;
|
||||
},
|
||||
}
|
||||
|
||||
_wantsDateSeparator: function(prevEvent, nextEventDate) {
|
||||
_wantsDateSeparator(prevEvent, nextEventDate) {
|
||||
if (prevEvent == null) {
|
||||
// first event in the panel: depends if we could back-paginate from
|
||||
// here.
|
||||
return !this.props.suppressFirstDateSeparator;
|
||||
}
|
||||
return wantsDateSeparator(prevEvent.getDate(), nextEventDate);
|
||||
},
|
||||
}
|
||||
|
||||
// Get a list of read receipts that should be shown next to this event
|
||||
// Receipts are objects which have a 'userId', 'roomMember' and 'ts'.
|
||||
_getReadReceiptsForEvent: function(event) {
|
||||
_getReadReceiptsForEvent(event) {
|
||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
|
||||
// get list of read receipts, sorted most recent first
|
||||
|
@ -728,12 +737,12 @@ module.exports = createReactClass({
|
|||
});
|
||||
});
|
||||
return receipts;
|
||||
},
|
||||
}
|
||||
|
||||
// Get an object that maps from event ID to a list of read receipts that
|
||||
// should be shown next to that event. If a hidden event has read receipts,
|
||||
// they are folded into the receipts of the last shown event.
|
||||
_getReadReceiptsByShownEvent: function() {
|
||||
_getReadReceiptsByShownEvent() {
|
||||
const receiptsByEvent = {};
|
||||
const receiptsByUserId = {};
|
||||
|
||||
|
@ -786,9 +795,9 @@ module.exports = createReactClass({
|
|||
}
|
||||
|
||||
return receiptsByEvent;
|
||||
},
|
||||
}
|
||||
|
||||
_getReadMarkerTile: function(visible) {
|
||||
_getReadMarkerTile(visible) {
|
||||
let hr;
|
||||
if (visible) {
|
||||
hr = <hr className="mx_RoomView_myReadMarker"
|
||||
|
@ -802,9 +811,9 @@ module.exports = createReactClass({
|
|||
{ hr }
|
||||
</li>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
_startAnimation: function(ghostNode) {
|
||||
_startAnimation = (ghostNode) => {
|
||||
if (this._readMarkerGhostNode) {
|
||||
Velocity.Utilities.removeData(this._readMarkerGhostNode);
|
||||
}
|
||||
|
@ -816,9 +825,9 @@ module.exports = createReactClass({
|
|||
{duration: 400, easing: 'easeInSine',
|
||||
delay: 1000});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_getReadMarkerGhostTile: function() {
|
||||
_getReadMarkerGhostTile() {
|
||||
const hr = <hr className="mx_RoomView_myReadMarker"
|
||||
style={{opacity: 1, width: '99%'}}
|
||||
ref={this._startAnimation}
|
||||
|
@ -833,31 +842,31 @@ module.exports = createReactClass({
|
|||
{ hr }
|
||||
</li>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
_collectEventNode: function(eventId, node) {
|
||||
_collectEventNode = (eventId, node) => {
|
||||
this.eventNodes[eventId] = node;
|
||||
},
|
||||
}
|
||||
|
||||
// once dynamic content in the events load, make the scrollPanel check the
|
||||
// scroll offsets.
|
||||
_onHeightChanged: function() {
|
||||
_onHeightChanged = () => {
|
||||
const scrollPanel = this.refs.scrollPanel;
|
||||
if (scrollPanel) {
|
||||
scrollPanel.checkScroll();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_onTypingShown: function() {
|
||||
_onTypingShown = () => {
|
||||
const scrollPanel = this.refs.scrollPanel;
|
||||
// this will make the timeline grow, so checkScroll
|
||||
scrollPanel.checkScroll();
|
||||
if (scrollPanel && scrollPanel.getScrollState().stuckAtBottom) {
|
||||
scrollPanel.preventShrinking();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_onTypingHidden: function() {
|
||||
_onTypingHidden = () => {
|
||||
const scrollPanel = this.refs.scrollPanel;
|
||||
if (scrollPanel) {
|
||||
// as hiding the typing notifications doesn't
|
||||
|
@ -868,9 +877,9 @@ module.exports = createReactClass({
|
|||
// reveal added padding to balance the notifs disappearing.
|
||||
scrollPanel.checkScroll();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
updateTimelineMinHeight: function() {
|
||||
updateTimelineMinHeight() {
|
||||
const scrollPanel = this.refs.scrollPanel;
|
||||
|
||||
if (scrollPanel) {
|
||||
|
@ -885,16 +894,16 @@ module.exports = createReactClass({
|
|||
scrollPanel.preventShrinking();
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
onTimelineReset: function() {
|
||||
onTimelineReset() {
|
||||
const scrollPanel = this.refs.scrollPanel;
|
||||
if (scrollPanel) {
|
||||
scrollPanel.clearPreventShrinking();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||
const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile");
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
|
@ -941,5 +950,5 @@ module.exports = createReactClass({
|
|||
{ bottomSpinner }
|
||||
</ScrollPanel>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
_fetch: function() {
|
||||
this.context.matrixClient.getJoinedGroups().done((result) => {
|
||||
this.context.matrixClient.getJoinedGroups().then((result) => {
|
||||
this.setState({groups: result.groups, error: null});
|
||||
}, (err) => {
|
||||
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
|
||||
|
|
|
@ -27,7 +27,6 @@ const dis = require('../../dispatcher');
|
|||
|
||||
import { linkifyAndSanitizeHtml } from '../../HtmlUtils';
|
||||
import PropTypes from 'prop-types';
|
||||
import Promise from 'bluebird';
|
||||
import { _t } from '../../languageHandler';
|
||||
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
||||
import Analytics from '../../Analytics';
|
||||
|
@ -89,7 +88,7 @@ module.exports = createReactClass({
|
|||
this.setState({protocolsLoading: false});
|
||||
return;
|
||||
}
|
||||
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
|
||||
MatrixClientPeg.get().getThirdpartyProtocols().then((response) => {
|
||||
this.protocols = response;
|
||||
this.setState({protocolsLoading: false});
|
||||
}, (err) => {
|
||||
|
@ -135,7 +134,7 @@ module.exports = createReactClass({
|
|||
publicRooms: [],
|
||||
loading: true,
|
||||
});
|
||||
this.getMoreRooms().done();
|
||||
this.getMoreRooms();
|
||||
},
|
||||
|
||||
getMoreRooms: function() {
|
||||
|
@ -246,7 +245,7 @@ module.exports = createReactClass({
|
|||
if (!alias) return;
|
||||
step = _t('delete the alias.');
|
||||
return MatrixClientPeg.get().deleteAlias(alias);
|
||||
}).done(() => {
|
||||
}).then(() => {
|
||||
modal.close();
|
||||
this.refreshRoomList();
|
||||
}, (err) => {
|
||||
|
@ -348,7 +347,7 @@ module.exports = createReactClass({
|
|||
});
|
||||
return;
|
||||
}
|
||||
MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => {
|
||||
MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).then((resp) => {
|
||||
if (resp.length > 0 && resp[0].alias) {
|
||||
this.showRoomAlias(resp[0].alias, true);
|
||||
} else {
|
||||
|
|
|
@ -27,7 +27,6 @@ import React from 'react';
|
|||
import createReactClass from 'create-react-class';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import Promise from 'bluebird';
|
||||
import classNames from 'classnames';
|
||||
import {Room} from "matrix-js-sdk";
|
||||
import { _t } from '../../languageHandler';
|
||||
|
@ -1102,7 +1101,7 @@ module.exports = createReactClass({
|
|||
}
|
||||
|
||||
ContentMessages.sharedInstance().sendStickerContentToRoom(url, this.state.room.roomId, info, text, MatrixClientPeg.get())
|
||||
.done(undefined, (error) => {
|
||||
.then(undefined, (error) => {
|
||||
if (error.name === "UnknownDeviceError") {
|
||||
// Let the staus bar handle this
|
||||
return;
|
||||
|
@ -1135,7 +1134,7 @@ module.exports = createReactClass({
|
|||
|
||||
debuglog("sending search request");
|
||||
const searchPromise = eventSearch(term, roomId);
|
||||
this._handleSearchResult(searchPromise).done();
|
||||
this._handleSearchResult(searchPromise);
|
||||
},
|
||||
|
||||
_handleSearchResult: function(searchPromise) {
|
||||
|
@ -1306,7 +1305,7 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
onForgetClick: function() {
|
||||
MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
|
||||
MatrixClientPeg.get().forget(this.state.room.roomId).then(function() {
|
||||
dis.dispatch({ action: 'view_next_room' });
|
||||
}, function(err) {
|
||||
const errCode = err.errcode || _t("unknown error code");
|
||||
|
@ -1323,7 +1322,7 @@ module.exports = createReactClass({
|
|||
this.setState({
|
||||
rejecting: true,
|
||||
});
|
||||
MatrixClientPeg.get().leave(this.state.roomId).done(function() {
|
||||
MatrixClientPeg.get().leave(this.state.roomId).then(function() {
|
||||
dis.dispatch({ action: 'view_next_room' });
|
||||
self.setState({
|
||||
rejecting: false,
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import Promise from 'bluebird';
|
||||
import { KeyCode } from '../../Keyboard';
|
||||
import Timer from '../../utils/Timer';
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
|
|
|
@ -23,7 +23,6 @@ import React from 'react';
|
|||
import createReactClass from 'create-react-class';
|
||||
import ReactDOM from "react-dom";
|
||||
import PropTypes from 'prop-types';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
const Matrix = require("matrix-js-sdk");
|
||||
const EventTimeline = Matrix.EventTimeline;
|
||||
|
@ -462,7 +461,7 @@ const TimelinePanel = createReactClass({
|
|||
// timeline window.
|
||||
//
|
||||
// see https://github.com/vector-im/vector-web/issues/1035
|
||||
this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).done(() => {
|
||||
this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).then(() => {
|
||||
if (this.unmounted) { return; }
|
||||
|
||||
const { events, liveEvents } = this._getEvents();
|
||||
|
|
84
src/components/structures/ToastContainer.js
Normal file
84
src/components/structures/ToastContainer.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 * as React from "react";
|
||||
import dis from "../../dispatcher";
|
||||
import { _t } from '../../languageHandler';
|
||||
import classNames from "classnames";
|
||||
|
||||
export default class ToastContainer extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {toasts: []};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._dispatcherRef = dis.register(this.onAction);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
dis.unregister(this._dispatcherRef);
|
||||
}
|
||||
|
||||
onAction = (payload) => {
|
||||
if (payload.action === "show_toast") {
|
||||
this._addToast(payload.toast);
|
||||
}
|
||||
};
|
||||
|
||||
_addToast(toast) {
|
||||
this.setState({toasts: this.state.toasts.concat(toast)});
|
||||
}
|
||||
|
||||
dismissTopToast = () => {
|
||||
const [, ...remaining] = this.state.toasts;
|
||||
this.setState({toasts: remaining});
|
||||
};
|
||||
|
||||
render() {
|
||||
const totalCount = this.state.toasts.length;
|
||||
const isStacked = totalCount > 1;
|
||||
let toast;
|
||||
if (totalCount !== 0) {
|
||||
const topToast = this.state.toasts[0];
|
||||
const {title, icon, key, component, props} = topToast;
|
||||
const toastClasses = classNames("mx_Toast_toast", {
|
||||
"mx_Toast_hasIcon": icon,
|
||||
[`mx_Toast_icon_${icon}`]: icon,
|
||||
});
|
||||
const countIndicator = isStacked ? _t(" (1/%(totalCount)s)", {totalCount}) : null;
|
||||
|
||||
const toastProps = Object.assign({}, props, {
|
||||
dismiss: this.dismissTopToast,
|
||||
key,
|
||||
});
|
||||
toast = (<div className={toastClasses}>
|
||||
<h2>{title}{countIndicator}</h2>
|
||||
<div className="mx_Toast_body">{React.createElement(component, toastProps)}</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
const containerClasses = classNames("mx_ToastContainer", {
|
||||
"mx_ToastContainer_stacked": isStacked,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={containerClasses} role="alert">
|
||||
{toast}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -105,7 +105,7 @@ module.exports = createReactClass({
|
|||
phase: PHASE_SENDING_EMAIL,
|
||||
});
|
||||
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
|
||||
this.reset.resetPassword(email, password).done(() => {
|
||||
this.reset.resetPassword(email, password).then(() => {
|
||||
this.setState({
|
||||
phase: PHASE_EMAIL_SENT,
|
||||
});
|
||||
|
|
|
@ -253,7 +253,7 @@ module.exports = createReactClass({
|
|||
this.setState({
|
||||
busy: false,
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
onUsernameChanged: function(username) {
|
||||
|
@ -439,7 +439,7 @@ module.exports = createReactClass({
|
|||
this.setState({
|
||||
busy: false,
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
_isSupportedFlow: function(flow) {
|
||||
|
|
|
@ -43,7 +43,7 @@ module.exports = createReactClass({
|
|||
const cli = MatrixClientPeg.get();
|
||||
this.setState({busy: true});
|
||||
const self = this;
|
||||
cli.getProfileInfo(cli.credentials.userId).done(function(result) {
|
||||
cli.getProfileInfo(cli.credentials.userId).then(function(result) {
|
||||
self.setState({
|
||||
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(result.avatar_url),
|
||||
busy: false,
|
||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import Matrix from 'matrix-js-sdk';
|
||||
import Promise from 'bluebird';
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -371,7 +370,7 @@ module.exports = createReactClass({
|
|||
if (pushers[i].kind === 'email') {
|
||||
const emailPusher = pushers[i];
|
||||
emailPusher.data = { brand: this.props.brand };
|
||||
matrixClient.setPusher(emailPusher).done(() => {
|
||||
matrixClient.setPusher(emailPusher).then(() => {
|
||||
console.log("Set email branding to " + this.props.brand);
|
||||
}, (error) => {
|
||||
console.error("Couldn't set email branding: " + error);
|
||||
|
|
|
@ -441,7 +441,7 @@ export const MsisdnAuthEntry = createReactClass({
|
|||
this.props.fail(e);
|
||||
}).finally(() => {
|
||||
this.setState({requestingToken: false});
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
|
|
|
@ -160,7 +160,7 @@ module.exports = createReactClass({
|
|||
|
||||
_onClickForget: function() {
|
||||
// FIXME: duplicated with RoomSettings (and dead code in RoomView)
|
||||
MatrixClientPeg.get().forget(this.props.room.roomId).done(() => {
|
||||
MatrixClientPeg.get().forget(this.props.room.roomId).then(() => {
|
||||
// Switch to another room view if we're currently viewing the
|
||||
// historical room
|
||||
if (RoomViewStore.getRoomId() === this.props.room.roomId) {
|
||||
|
@ -190,7 +190,7 @@ module.exports = createReactClass({
|
|||
this.setState({
|
||||
roomNotifState: newState,
|
||||
});
|
||||
RoomNotifs.setRoomNotifsState(roomId, newState).done(() => {
|
||||
RoomNotifs.setRoomNotifsState(roomId, newState).then(() => {
|
||||
// delay slightly so that the user can see their state change
|
||||
// before closing the menu
|
||||
return sleep(500).then(() => {
|
||||
|
|
134
src/components/views/context_menus/WidgetContextMenu.js
Normal file
134
src/components/views/context_menus/WidgetContextMenu.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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';
|
||||
|
||||
export default class WidgetContextMenu extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func,
|
||||
|
||||
// Callback for when the revoke button is clicked. Required.
|
||||
onRevokeClicked: PropTypes.func.isRequired,
|
||||
|
||||
// Callback for when the snapshot button is clicked. Button not shown
|
||||
// without a callback.
|
||||
onSnapshotClicked: PropTypes.func,
|
||||
|
||||
// Callback for when the reload button is clicked. Button not shown
|
||||
// without a callback.
|
||||
onReloadClicked: PropTypes.func,
|
||||
|
||||
// Callback for when the edit button is clicked. Button not shown
|
||||
// without a callback.
|
||||
onEditClicked: PropTypes.func,
|
||||
|
||||
// Callback for when the delete button is clicked. Button not shown
|
||||
// without a callback.
|
||||
onDeleteClicked: PropTypes.func,
|
||||
};
|
||||
|
||||
proxyClick(fn) {
|
||||
fn();
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
}
|
||||
|
||||
// XXX: It's annoying that our context menus require us to hit onFinished() to close :(
|
||||
|
||||
onEditClicked = () => {
|
||||
this.proxyClick(this.props.onEditClicked);
|
||||
};
|
||||
|
||||
onReloadClicked = () => {
|
||||
this.proxyClick(this.props.onReloadClicked);
|
||||
};
|
||||
|
||||
onSnapshotClicked = () => {
|
||||
this.proxyClick(this.props.onSnapshotClicked);
|
||||
};
|
||||
|
||||
onDeleteClicked = () => {
|
||||
this.proxyClick(this.props.onDeleteClicked);
|
||||
};
|
||||
|
||||
onRevokeClicked = () => {
|
||||
this.proxyClick(this.props.onRevokeClicked);
|
||||
};
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
||||
|
||||
const options = [];
|
||||
|
||||
if (this.props.onEditClicked) {
|
||||
options.push(
|
||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onEditClicked} key='edit'>
|
||||
{_t("Edit")}
|
||||
</AccessibleButton>,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.onReloadClicked) {
|
||||
options.push(
|
||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onReloadClicked}
|
||||
key='reload'>
|
||||
{_t("Reload")}
|
||||
</AccessibleButton>,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.onSnapshotClicked) {
|
||||
options.push(
|
||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onSnapshotClicked}
|
||||
key='snap'>
|
||||
{_t("Take picture")}
|
||||
</AccessibleButton>,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.onDeleteClicked) {
|
||||
options.push(
|
||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onDeleteClicked}
|
||||
key='delete'>
|
||||
{_t("Remove for everyone")}
|
||||
</AccessibleButton>,
|
||||
);
|
||||
}
|
||||
|
||||
// Push this last so it appears last. It's always present.
|
||||
options.push(
|
||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onRevokeClicked} key='revoke'>
|
||||
{_t("Remove for me")}
|
||||
</AccessibleButton>,
|
||||
);
|
||||
|
||||
// Put separators between the options
|
||||
if (options.length > 1) {
|
||||
const length = options.length;
|
||||
for (let i = 0; i < length - 1; i++) {
|
||||
const sep = <hr key={i} className="mx_WidgetContextMenu_separator" />;
|
||||
|
||||
// Insert backwards so the insertions don't affect our math on where to place them.
|
||||
// We also use our cached length to avoid worrying about options.length changing
|
||||
options.splice(length - 1 - i, 0, sep);
|
||||
}
|
||||
}
|
||||
|
||||
return <div className="mx_WidgetContextMenu">{options}</div>;
|
||||
}
|
||||
}
|
|
@ -266,7 +266,7 @@ module.exports = createReactClass({
|
|||
this.setState({
|
||||
searchError: err.errcode ? err.message : _t('Something went wrong!'),
|
||||
});
|
||||
}).done(() => {
|
||||
}).then(() => {
|
||||
this.setState({
|
||||
busy: false,
|
||||
});
|
||||
|
@ -379,7 +379,7 @@ module.exports = createReactClass({
|
|||
// Do a local search immediately
|
||||
this._doLocalSearch(query);
|
||||
}
|
||||
}).done(() => {
|
||||
}).then(() => {
|
||||
this.setState({
|
||||
busy: false,
|
||||
});
|
||||
|
|
|
@ -93,7 +93,7 @@ export default createReactClass({
|
|||
this.setState({createError: e});
|
||||
}).finally(() => {
|
||||
this.setState({creating: false});
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
_onCancel: function() {
|
||||
|
|
57
src/components/views/dialogs/IntegrationsDisabledDialog.js
Normal file
57
src/components/views/dialogs/IntegrationsDisabledDialog.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 {_t} from "../../../languageHandler";
|
||||
import sdk from "../../../index";
|
||||
import dis from '../../../dispatcher';
|
||||
|
||||
export default class IntegrationsDisabledDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
_onAcknowledgeClick = () => {
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
||||
_onOpenSettingsClick = () => {
|
||||
this.props.onFinished();
|
||||
dis.dispatch({action: "view_user_settings"});
|
||||
};
|
||||
|
||||
render() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
return (
|
||||
<BaseDialog className='mx_IntegrationsDisabledDialog' hasCancel={true}
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("Integrations are disabled")}>
|
||||
<div className='mx_IntegrationsDisabledDialog_content'>
|
||||
<p>{_t("Enable 'Manage Integrations' in Settings to do this.")}</p>
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("Settings")}
|
||||
onPrimaryButtonClick={this._onOpenSettingsClick}
|
||||
cancelButton={_t("OK")}
|
||||
onCancel={this._onAcknowledgeClick}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
55
src/components/views/dialogs/IntegrationsImpossibleDialog.js
Normal file
55
src/components/views/dialogs/IntegrationsImpossibleDialog.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 {_t} from "../../../languageHandler";
|
||||
import sdk from "../../../index";
|
||||
|
||||
export default class IntegrationsImpossibleDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
_onAcknowledgeClick = () => {
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
||||
render() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
return (
|
||||
<BaseDialog className='mx_IntegrationsImpossibleDialog' hasCancel={false}
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("Integrations not allowed")}>
|
||||
<div className='mx_IntegrationsImpossibleDialog_content'>
|
||||
<p>
|
||||
{_t(
|
||||
"Your Riot doesn't allow you to use an Integration Manager to do this. " +
|
||||
"Please contact an admin.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("OK")}
|
||||
onPrimaryButtonClick={this._onAcknowledgeClick}
|
||||
hasCancel={false}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -78,7 +78,7 @@ export default createReactClass({
|
|||
true,
|
||||
);
|
||||
}
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
|
|
@ -62,7 +62,7 @@ export default createReactClass({
|
|||
return;
|
||||
}
|
||||
this._addThreepid = new AddThreepid();
|
||||
this._addThreepid.addEmailAddress(emailAddress).done(() => {
|
||||
this._addThreepid.addEmailAddress(emailAddress).then(() => {
|
||||
Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, {
|
||||
title: _t("Verification Pending"),
|
||||
description: _t(
|
||||
|
@ -96,7 +96,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
verifyEmailAddress: function() {
|
||||
this._addThreepid.checkEmailLinkClicked().done(() => {
|
||||
this._addThreepid.checkEmailLinkClicked().then(() => {
|
||||
this.props.onFinished(true);
|
||||
}, (err) => {
|
||||
this.setState({emailBusy: false});
|
||||
|
|
|
@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
|
|
|
@ -82,10 +82,10 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
|
|||
|
||||
client.setTermsInteractionCallback((policyInfo, agreedUrls) => {
|
||||
// To avoid visual glitching of two modals stacking briefly, we customise the
|
||||
// terms dialog sizing when it will appear for the integrations manager so that
|
||||
// terms dialog sizing when it will appear for the integration manager so that
|
||||
// it gets the same basic size as the IM's own modal.
|
||||
return dialogTermsInteractionCallback(
|
||||
policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationsManager',
|
||||
policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationManager',
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -139,7 +139,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
|
|||
}
|
||||
|
||||
_renderTab() {
|
||||
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
||||
const IntegrationManager = sdk.getComponent("views.settings.IntegrationManager");
|
||||
let uiUrl = null;
|
||||
if (this.state.currentScalarClient) {
|
||||
uiUrl = this.state.currentScalarClient.getScalarInterfaceUrlForRoom(
|
||||
|
@ -148,7 +148,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
|
|||
this.props.integrationId,
|
||||
);
|
||||
}
|
||||
return <IntegrationsManager
|
||||
return <IntegrationManager
|
||||
configured={true}
|
||||
loading={this.state.currentLoading}
|
||||
connected={this.state.currentConnected}
|
||||
|
|
|
@ -86,7 +86,7 @@ export default class TermsDialog extends React.PureComponent {
|
|||
case Matrix.SERVICE_TYPES.IS:
|
||||
return <div>{_t("Identity Server")}<br />({host})</div>;
|
||||
case Matrix.SERVICE_TYPES.IM:
|
||||
return <div>{_t("Integrations Manager")}<br />({host})</div>;
|
||||
return <div>{_t("Integration Manager")}<br />({host})</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ export default class AppPermission extends React.Component {
|
|||
creatorUserId: PropTypes.string.isRequired,
|
||||
roomId: PropTypes.string.isRequired,
|
||||
onPermissionGranted: PropTypes.func.isRequired,
|
||||
isRoomEncrypted: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -114,6 +115,8 @@ export default class AppPermission extends React.Component {
|
|||
: _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s.",
|
||||
{widgetDomain: this.state.widgetDomain}, {helpIcon: () => warningTooltip});
|
||||
|
||||
const encryptionWarning = this.props.isRoomEncrypted ? _t("Widgets do not use message encryption.") : null;
|
||||
|
||||
return (
|
||||
<div className='mx_AppPermissionWarning'>
|
||||
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_bolder mx_AppPermissionWarning_smallText'>
|
||||
|
@ -128,7 +131,7 @@ export default class AppPermission extends React.Component {
|
|||
{warning}
|
||||
</div>
|
||||
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_smallText'>
|
||||
{_t("This widget may use cookies.")}
|
||||
{_t("This widget may use cookies.")} {encryptionWarning}
|
||||
</div>
|
||||
<div className='mx_AppPermissionWarning_row'>
|
||||
<AccessibleButton kind='primary_sm' onClick={this.props.onPermissionGranted}>
|
||||
|
|
|
@ -35,6 +35,7 @@ import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
|
|||
import classNames from 'classnames';
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
import {createMenu} from "../../structures/ContextualMenu";
|
||||
|
||||
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
||||
const ENABLE_REACT_PERF = false;
|
||||
|
@ -52,7 +53,7 @@ export default class AppTile extends React.Component {
|
|||
this._onLoaded = this._onLoaded.bind(this);
|
||||
this._onEditClick = this._onEditClick.bind(this);
|
||||
this._onDeleteClick = this._onDeleteClick.bind(this);
|
||||
this._onCancelClick = this._onCancelClick.bind(this);
|
||||
this._onRevokeClicked = this._onRevokeClicked.bind(this);
|
||||
this._onSnapshotClick = this._onSnapshotClick.bind(this);
|
||||
this.onClickMenuBar = this.onClickMenuBar.bind(this);
|
||||
this._onMinimiseClick = this._onMinimiseClick.bind(this);
|
||||
|
@ -207,7 +208,7 @@ export default class AppTile extends React.Component {
|
|||
if (!this._scalarClient) {
|
||||
this._scalarClient = defaultManager.getScalarClient();
|
||||
}
|
||||
this._scalarClient.getScalarToken().done((token) => {
|
||||
this._scalarClient.getScalarToken().then((token) => {
|
||||
// Append scalar_token as a query param if not already present
|
||||
this._scalarClient.scalarToken = token;
|
||||
const u = url.parse(this._addWurlParams(this.props.url));
|
||||
|
@ -271,7 +272,7 @@ export default class AppTile extends React.Component {
|
|||
return WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
|
||||
}
|
||||
|
||||
_onEditClick(e) {
|
||||
_onEditClick() {
|
||||
console.log("Edit widget ID ", this.props.id);
|
||||
if (this.props.onEditClick) {
|
||||
this.props.onEditClick();
|
||||
|
@ -293,7 +294,7 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
_onSnapshotClick(e) {
|
||||
_onSnapshotClick() {
|
||||
console.warn("Requesting widget snapshot");
|
||||
ActiveWidgetStore.getWidgetMessaging(this.props.id).getScreenshot()
|
||||
.catch((err) => {
|
||||
|
@ -360,13 +361,9 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
_onCancelClick() {
|
||||
if (this.props.onDeleteClick) {
|
||||
this.props.onDeleteClick();
|
||||
} else {
|
||||
console.info("Revoke widget permissions - %s", this.props.id);
|
||||
this._revokeWidgetPermission();
|
||||
}
|
||||
_onRevokeClicked() {
|
||||
console.info("Revoke widget permissions - %s", this.props.id);
|
||||
this._revokeWidgetPermission();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -544,18 +541,59 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
_onPopoutWidgetClick(e) {
|
||||
_onPopoutWidgetClick() {
|
||||
// Using Object.assign workaround as the following opens in a new window instead of a new tab.
|
||||
// window.open(this._getSafeUrl(), '_blank', 'noopener=yes');
|
||||
Object.assign(document.createElement('a'),
|
||||
{ target: '_blank', href: this._getSafeUrl(), rel: 'noopener'}).click();
|
||||
}
|
||||
|
||||
_onReloadWidgetClick(e) {
|
||||
_onReloadWidgetClick() {
|
||||
// Reload iframe in this way to avoid cross-origin restrictions
|
||||
this.refs.appFrame.src = this.refs.appFrame.src;
|
||||
}
|
||||
|
||||
_getMenuOptions(ev) {
|
||||
// TODO: This block of code gets copy/pasted a lot. We should make that happen less.
|
||||
const menuOptions = {};
|
||||
const buttonRect = ev.target.getBoundingClientRect();
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const buttonLeft = buttonRect.left + window.pageXOffset;
|
||||
const buttonTop = buttonRect.top + window.pageYOffset;
|
||||
// Align the right edge of the menu to the left edge of the button
|
||||
menuOptions.right = window.innerWidth - buttonLeft;
|
||||
// Align the menu vertically on whichever side of the button has more
|
||||
// space available.
|
||||
if (buttonTop < window.innerHeight / 2) {
|
||||
menuOptions.top = buttonTop;
|
||||
} else {
|
||||
menuOptions.bottom = window.innerHeight - buttonTop;
|
||||
}
|
||||
return menuOptions;
|
||||
}
|
||||
|
||||
_onContextMenuClick = (ev) => {
|
||||
const WidgetContextMenu = sdk.getComponent('views.context_menus.WidgetContextMenu');
|
||||
const menuOptions = {
|
||||
...this._getMenuOptions(ev),
|
||||
|
||||
// A revoke handler is always required
|
||||
onRevokeClicked: this._onRevokeClicked,
|
||||
};
|
||||
|
||||
const canUserModify = this._canUserModify();
|
||||
const showEditButton = Boolean(this._scalarClient && canUserModify);
|
||||
const showDeleteButton = (this.props.showDelete === undefined || this.props.showDelete) && canUserModify;
|
||||
const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show;
|
||||
|
||||
if (showEditButton) menuOptions.onEditClicked = this._onEditClick;
|
||||
if (showDeleteButton) menuOptions.onDeleteClicked = this._onDeleteClick;
|
||||
if (showPictureSnapshotButton) menuOptions.onSnapshotClicked = this._onSnapshotClick;
|
||||
if (this.props.showReload) menuOptions.onReloadClicked = this._onReloadWidgetClick;
|
||||
|
||||
createMenu(WidgetContextMenu, menuOptions);
|
||||
};
|
||||
|
||||
render() {
|
||||
let appTileBody;
|
||||
|
||||
|
@ -565,7 +603,7 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
|
||||
// Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin
|
||||
// because that would allow the iframe to prgramatically remove the sandbox attribute, but
|
||||
// because that would allow the iframe to programmatically remove the sandbox attribute, but
|
||||
// this would only be for content hosted on the same origin as the riot client: anything
|
||||
// hosted on the same origin as the client will get the same access as if you clicked
|
||||
// a link to it.
|
||||
|
@ -585,12 +623,14 @@ export default class AppTile extends React.Component {
|
|||
</div>
|
||||
);
|
||||
if (!this.state.hasPermissionToLoad) {
|
||||
const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
||||
appTileBody = (
|
||||
<div className={appTileBodyClass}>
|
||||
<AppPermission
|
||||
roomId={this.props.room.roomId}
|
||||
creatorUserId={this.props.creatorUserId}
|
||||
url={this.state.widgetUrl}
|
||||
isRoomEncrypted={isEncrypted}
|
||||
onPermissionGranted={this._grantWidgetPermission}
|
||||
/>
|
||||
</div>
|
||||
|
@ -643,13 +683,6 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
// editing is done in scalar
|
||||
const canUserModify = this._canUserModify();
|
||||
const showEditButton = Boolean(this._scalarClient && canUserModify);
|
||||
const showDeleteButton = (this.props.showDelete === undefined || this.props.showDelete) && canUserModify;
|
||||
const showCancelButton = (this.props.showCancel === undefined || this.props.showCancel) && !showDeleteButton;
|
||||
// Picture snapshot - only show button when apps are maximised.
|
||||
const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show;
|
||||
const showMinimiseButton = this.props.showMinimise && this.props.show;
|
||||
const showMaximiseButton = this.props.showMinimise && !this.props.show;
|
||||
|
||||
|
@ -688,41 +721,17 @@ export default class AppTile extends React.Component {
|
|||
{ this.props.showTitle && this._getTileTitle() }
|
||||
</span>
|
||||
<span className="mx_AppTileMenuBarWidgets">
|
||||
{ /* Reload widget */ }
|
||||
{ this.props.showReload && <AccessibleButton
|
||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_reload"
|
||||
title={_t('Reload widget')}
|
||||
onClick={this._onReloadWidgetClick}
|
||||
/> }
|
||||
{ /* Popout widget */ }
|
||||
{ this.props.showPopout && <AccessibleButton
|
||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout"
|
||||
title={_t('Popout widget')}
|
||||
onClick={this._onPopoutWidgetClick}
|
||||
/> }
|
||||
{ /* Snapshot widget */ }
|
||||
{ showPictureSnapshotButton && <AccessibleButton
|
||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_snapshot"
|
||||
title={_t('Picture')}
|
||||
onClick={this._onSnapshotClick}
|
||||
/> }
|
||||
{ /* Edit widget */ }
|
||||
{ showEditButton && <AccessibleButton
|
||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_edit"
|
||||
title={_t('Edit')}
|
||||
onClick={this._onEditClick}
|
||||
/> }
|
||||
{ /* Delete widget */ }
|
||||
{ showDeleteButton && <AccessibleButton
|
||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_delete"
|
||||
title={_t('Delete widget')}
|
||||
onClick={this._onDeleteClick}
|
||||
/> }
|
||||
{ /* Cancel widget */ }
|
||||
{ showCancelButton && <AccessibleButton
|
||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_cancel"
|
||||
title={_t('Revoke widget access')}
|
||||
onClick={this._onCancelClick}
|
||||
{ /* Context menu */ }
|
||||
{ <AccessibleButton
|
||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
|
||||
title={_t('More options')}
|
||||
onClick={this._onContextMenuClick}
|
||||
/> }
|
||||
</span>
|
||||
</div> }
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
/**
|
||||
* A component which wraps an EditableText, with a spinner while updates take
|
||||
|
@ -51,7 +50,7 @@ export default class EditableTextContainer extends React.Component {
|
|||
|
||||
this.setState({busy: true});
|
||||
|
||||
this.props.getInitialValue().done(
|
||||
this.props.getInitialValue().then(
|
||||
(result) => {
|
||||
if (this._unmounted) { return; }
|
||||
this.setState({
|
||||
|
@ -83,7 +82,7 @@ export default class EditableTextContainer extends React.Component {
|
|||
errorString: null,
|
||||
});
|
||||
|
||||
this.props.onSubmit(value).done(
|
||||
this.props.onSubmit(value).then(
|
||||
() => {
|
||||
if (this._unmounted) { return; }
|
||||
this.setState({
|
||||
|
|
|
@ -54,7 +54,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
|||
if (!PlatformPeg.get()) return;
|
||||
|
||||
MatrixClientPeg.get().stopClient();
|
||||
MatrixClientPeg.get().store.deleteAllData().done(() => {
|
||||
MatrixClientPeg.get().store.deleteAllData().then(() => {
|
||||
PlatformPeg.get().reload();
|
||||
});
|
||||
};
|
||||
|
|
28
src/components/views/elements/FormButton.js
Normal file
28
src/components/views/elements/FormButton.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 AccessibleButton from "./AccessibleButton";
|
||||
|
||||
export default function FormButton(props) {
|
||||
const {className, label, kind, ...restProps} = props;
|
||||
const newClassName = (className || "") + " mx_FormButton";
|
||||
const allProps = Object.assign({}, restProps,
|
||||
{className: newClassName, kind: kind || "primary", children: [label]});
|
||||
return React.createElement(AccessibleButton, allProps);
|
||||
}
|
||||
|
||||
FormButton.propTypes = AccessibleButton.propTypes;
|
|
@ -84,7 +84,7 @@ export default class ImageView extends React.Component {
|
|||
title: _t('Error'),
|
||||
description: _t('You cannot delete this image. (%(code)s)', {code: code}),
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -49,7 +49,7 @@ export default class LanguageDropdown extends React.Component {
|
|||
this.setState({langs});
|
||||
}).catch(() => {
|
||||
this.setState({langs: ['en']});
|
||||
}).done();
|
||||
});
|
||||
|
||||
if (!this.props.value) {
|
||||
// If no value is given, we start with the first
|
||||
|
|
|
@ -100,7 +100,9 @@ module.exports = createReactClass({
|
|||
const parent = ReactDOM.findDOMNode(this).parentNode;
|
||||
let style = {};
|
||||
style = this._updatePosition(style);
|
||||
style.display = "block";
|
||||
// Hide the entire container when not visible. This prevents flashing of the tooltip
|
||||
// if it is not meant to be visible on first mount.
|
||||
style.display = this.props.visible ? "block" : "none";
|
||||
|
||||
const tooltipClasses = classNames("mx_Tooltip", this.props.tooltipClassName, {
|
||||
"mx_Tooltip_visible": this.props.visible,
|
||||
|
|
|
@ -36,7 +36,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.context.matrixClient.getJoinedGroups().done((result) => {
|
||||
this.context.matrixClient.getJoinedGroups().then((result) => {
|
||||
this.setState({groups: result.groups || [], error: null});
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
|
|
|
@ -55,7 +55,7 @@ export default class MAudioBody extends React.Component {
|
|||
decryptFile(content.file).then(function(blob) {
|
||||
decryptedBlob = blob;
|
||||
return URL.createObjectURL(decryptedBlob);
|
||||
}).done((url) => {
|
||||
}).then((url) => {
|
||||
this.setState({
|
||||
decryptedUrl: url,
|
||||
decryptedBlob: decryptedBlob,
|
||||
|
|
|
@ -24,7 +24,6 @@ import MFileBody from './MFileBody';
|
|||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
import { decryptFile } from '../../../utils/DecryptFile';
|
||||
import Promise from 'bluebird';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
|
@ -289,7 +288,7 @@ export default class MImageBody extends React.Component {
|
|||
this.setState({
|
||||
error: err,
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
}
|
||||
|
||||
// Remember that the user wanted to show this particular image
|
||||
|
|
|
@ -111,10 +111,10 @@ export default class MKeyVerificationRequest extends React.Component {
|
|||
userLabelForEventRoom(fromUserId, mxEvent)}</div>);
|
||||
const isResolved = !(this.state.accepted || this.state.cancelled || this.state.done);
|
||||
if (isResolved) {
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
stateNode = (<div className="mx_KeyVerification_buttons">
|
||||
<AccessibleButton kind="decline" onClick={this._onRejectClicked}>{_t("Decline")}</AccessibleButton>
|
||||
<AccessibleButton kind="accept" onClick={this._onAcceptClicked}>{_t("Accept")}</AccessibleButton>
|
||||
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
|
||||
<FormButton onClick={this._onAcceptClicked} label={_t("Accept")} />
|
||||
</div>);
|
||||
}
|
||||
} else if (isOwn) { // request sent by us
|
||||
|
|
|
@ -20,7 +20,6 @@ import createReactClass from 'create-react-class';
|
|||
import MFileBody from './MFileBody';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import { decryptFile } from '../../../utils/DecryptFile';
|
||||
import Promise from 'bluebird';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
|
@ -89,7 +88,7 @@ module.exports = createReactClass({
|
|||
const content = this.props.mxEvent.getContent();
|
||||
if (content.file !== undefined && this.state.decryptedUrl === null) {
|
||||
let thumbnailPromise = Promise.resolve(null);
|
||||
if (content.info.thumbnail_file) {
|
||||
if (content.info && content.info.thumbnail_file) {
|
||||
thumbnailPromise = decryptFile(
|
||||
content.info.thumbnail_file,
|
||||
).then(function(blob) {
|
||||
|
@ -115,7 +114,7 @@ module.exports = createReactClass({
|
|||
this.setState({
|
||||
error: err,
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -43,7 +43,8 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent {
|
|||
if (room) {
|
||||
const senders = [];
|
||||
for (const reactionEvent of reactionEvents) {
|
||||
const { name } = room.getMember(reactionEvent.getSender());
|
||||
const member = room.getMember(reactionEvent.getSender());
|
||||
const name = member ? member.name : reactionEvent.getSender();
|
||||
senders.push(name);
|
||||
}
|
||||
const shortName = unicodeToShortcode(content);
|
||||
|
|
|
@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
|
|
|
@ -21,7 +21,6 @@ import PropTypes from 'prop-types';
|
|||
import classNames from 'classnames';
|
||||
import flatMap from 'lodash/flatMap';
|
||||
import type {Completion} from '../../../autocomplete/Autocompleter';
|
||||
import Promise from 'bluebird';
|
||||
import { Room } from 'matrix-js-sdk';
|
||||
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
|
|
@ -53,7 +53,7 @@ module.exports = createReactClass({
|
|||
);
|
||||
}, (error)=>{
|
||||
console.error("Failed to get URL preview: " + error);
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
|
|
@ -248,7 +248,7 @@ module.exports = createReactClass({
|
|||
return client.getStoredDevicesForUser(member.userId);
|
||||
}).finally(function() {
|
||||
self._cancelDeviceList = null;
|
||||
}).done(function(devices) {
|
||||
}).then(function(devices) {
|
||||
if (cancelled) {
|
||||
// we got cancelled - presumably a different user now
|
||||
return;
|
||||
|
@ -581,7 +581,7 @@ module.exports = createReactClass({
|
|||
},
|
||||
).finally(()=>{
|
||||
this.setState({ updating: this.state.updating - 1 });
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
onPowerChange: async function(powerLevel) {
|
||||
|
@ -638,7 +638,7 @@ module.exports = createReactClass({
|
|||
this.setState({ updating: this.state.updating + 1 });
|
||||
createRoom({dmUserId: this.props.member.userId}).finally(() => {
|
||||
this.setState({ updating: this.state.updating - 1 });
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
onLeaveClick: function() {
|
||||
|
|
|
@ -25,7 +25,6 @@ import RoomViewStore from '../../../stores/RoomViewStore';
|
|||
import Stickerpicker from './Stickerpicker';
|
||||
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
|
||||
import ContentMessages from '../../../ContentMessages';
|
||||
import classNames from 'classnames';
|
||||
import E2EIcon from './E2EIcon';
|
||||
|
||||
function ComposerAvatar(props) {
|
||||
|
@ -353,13 +352,9 @@ export default class MessageComposer extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
const wrapperClasses = classNames({
|
||||
mx_MessageComposer_wrapper: true,
|
||||
mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus,
|
||||
});
|
||||
return (
|
||||
<div className="mx_MessageComposer">
|
||||
<div className={wrapperClasses}>
|
||||
<div className="mx_MessageComposer_wrapper">
|
||||
<div className="mx_MessageComposer_row">
|
||||
{ controls }
|
||||
</div>
|
||||
|
|
|
@ -460,13 +460,9 @@ export default class SlateMessageComposer extends React.Component {
|
|||
|
||||
const showFormatBar = this.state.showFormatting && this.state.inputState.isRichTextEnabled;
|
||||
|
||||
const wrapperClasses = classNames({
|
||||
mx_MessageComposer_wrapper: true,
|
||||
mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus,
|
||||
});
|
||||
return (
|
||||
<div className="mx_MessageComposer">
|
||||
<div className={wrapperClasses}>
|
||||
<div className="mx_MessageComposer_wrapper">
|
||||
<div className="mx_MessageComposer_row">
|
||||
{ controls }
|
||||
</div>
|
||||
|
|
|
@ -74,10 +74,10 @@ export default class Stickerpicker extends React.Component {
|
|||
this.forceUpdate();
|
||||
return this.scalarClient;
|
||||
}).catch((e) => {
|
||||
this._imError(_td("Failed to connect to integrations server"), e);
|
||||
this._imError(_td("Failed to connect to integration manager"), e);
|
||||
});
|
||||
} else {
|
||||
this._imError(_td("No integrations server is configured to manage stickers with"));
|
||||
IntegrationManagers.sharedInstance().openNoManagerDialog();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,12 +287,17 @@ export default class Stickerpicker extends React.Component {
|
|||
return stickersContent;
|
||||
}
|
||||
|
||||
/**
|
||||
// Dev note: this isn't jsdoc because it's angry.
|
||||
/*
|
||||
* Show the sticker picker overlay
|
||||
* If no stickerpacks have been added, show a link to the integration manager add sticker packs page.
|
||||
* @param {Event} e Event that triggered the function
|
||||
*/
|
||||
_onShowStickersClick(e) {
|
||||
if (!SettingsStore.getValue("integrationProvisioning")) {
|
||||
// Intercept this case and spawn a warning.
|
||||
return IntegrationManagers.sharedInstance().showDisabledDialog();
|
||||
}
|
||||
|
||||
// XXX: Simplify by using a context menu that is positioned relative to the sticker picker button
|
||||
|
||||
const buttonRect = e.target.getBoundingClientRect();
|
||||
|
@ -346,7 +351,7 @@ export default class Stickerpicker extends React.Component {
|
|||
}
|
||||
|
||||
/**
|
||||
* Launch the integrations manager on the stickers integration page
|
||||
* Launch the integration manager on the stickers integration page
|
||||
*/
|
||||
_launchManageIntegrations() {
|
||||
// TODO: Open the right integration manager for the widget
|
||||
|
|
|
@ -112,7 +112,7 @@ module.exports = createReactClass({
|
|||
}
|
||||
});
|
||||
|
||||
httpPromise.done(function() {
|
||||
httpPromise.then(function() {
|
||||
self.setState({
|
||||
phase: self.Phases.Display,
|
||||
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl),
|
||||
|
|
|
@ -25,7 +25,6 @@ const Modal = require("../../../Modal");
|
|||
const sdk = require("../../../index");
|
||||
|
||||
import dis from "../../../dispatcher";
|
||||
import Promise from 'bluebird';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
|
@ -174,7 +173,7 @@ module.exports = createReactClass({
|
|||
newPassword: "",
|
||||
newPasswordConfirm: "",
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
_optionallySetEmail: function() {
|
||||
|
|
|
@ -52,7 +52,7 @@ export default class DevicesPanel extends React.Component {
|
|||
}
|
||||
|
||||
_loadDevices() {
|
||||
MatrixClientPeg.get().getDevices().done(
|
||||
MatrixClientPeg.get().getDevices().then(
|
||||
(resp) => {
|
||||
if (this._unmounted) { return; }
|
||||
this.setState({devices: resp.devices || []});
|
||||
|
|
|
@ -21,12 +21,9 @@ import sdk from '../../../index';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import dis from '../../../dispatcher';
|
||||
|
||||
export default class IntegrationsManager extends React.Component {
|
||||
export default class IntegrationManager extends React.Component {
|
||||
static propTypes = {
|
||||
// false to display an error saying that there is no integrations manager configured
|
||||
configured: PropTypes.bool.isRequired,
|
||||
|
||||
// false to display an error saying that we couldn't connect to the integrations manager
|
||||
// false to display an error saying that we couldn't connect to the integration manager
|
||||
connected: PropTypes.bool.isRequired,
|
||||
|
||||
// true to display a loading spinner
|
||||
|
@ -40,7 +37,6 @@ export default class IntegrationsManager extends React.Component {
|
|||
};
|
||||
|
||||
static defaultProps = {
|
||||
configured: true,
|
||||
connected: true,
|
||||
loading: false,
|
||||
};
|
||||
|
@ -70,20 +66,11 @@ export default class IntegrationsManager extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
if (!this.props.configured) {
|
||||
return (
|
||||
<div className='mx_IntegrationsManager_error'>
|
||||
<h3>{_t("No integrations server configured")}</h3>
|
||||
<p>{_t("This Riot instance does not have an integrations server configured.")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.loading) {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
return (
|
||||
<div className='mx_IntegrationsManager_loading'>
|
||||
<h3>{_t("Connecting to integrations server...")}</h3>
|
||||
<div className='mx_IntegrationManager_loading'>
|
||||
<h3>{_t("Connecting to integration manager...")}</h3>
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
|
@ -91,9 +78,9 @@ export default class IntegrationsManager extends React.Component {
|
|||
|
||||
if (!this.props.connected) {
|
||||
return (
|
||||
<div className='mx_IntegrationsManager_error'>
|
||||
<h3>{_t("Cannot connect to integrations server")}</h3>
|
||||
<p>{_t("The integrations server is offline or it cannot reach your homeserver.")}</p>
|
||||
<div className='mx_IntegrationManager_error'>
|
||||
<h3>{_t("Cannot connect to integration manager")}</h3>
|
||||
<p>{_t("The integration manager is offline or it cannot reach your homeserver.")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import Promise from 'bluebird';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
|
@ -97,7 +96,7 @@ module.exports = createReactClass({
|
|||
phase: this.phases.LOADING,
|
||||
});
|
||||
|
||||
MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).done(function() {
|
||||
MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).then(function() {
|
||||
self._refreshFromServer();
|
||||
});
|
||||
},
|
||||
|
@ -170,7 +169,7 @@ module.exports = createReactClass({
|
|||
emailPusher.kind = null;
|
||||
emailPusherPromise = MatrixClientPeg.get().setPusher(emailPusher);
|
||||
}
|
||||
emailPusherPromise.done(() => {
|
||||
emailPusherPromise.then(() => {
|
||||
this._refreshFromServer();
|
||||
}, (error) => {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
@ -274,7 +273,7 @@ module.exports = createReactClass({
|
|||
}
|
||||
}
|
||||
|
||||
Promise.all(deferreds).done(function() {
|
||||
Promise.all(deferreds).then(function() {
|
||||
self._refreshFromServer();
|
||||
}, function(error) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
@ -343,7 +342,7 @@ module.exports = createReactClass({
|
|||
}
|
||||
}
|
||||
|
||||
Promise.all(deferreds).done(function(resps) {
|
||||
Promise.all(deferreds).then(function(resps) {
|
||||
self._refreshFromServer();
|
||||
}, function(error) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
@ -398,7 +397,7 @@ module.exports = createReactClass({
|
|||
};
|
||||
|
||||
// Then, add the new ones
|
||||
Promise.all(removeDeferreds).done(function(resps) {
|
||||
Promise.all(removeDeferreds).then(function(resps) {
|
||||
const deferreds = [];
|
||||
|
||||
let pushRuleVectorStateKind = self.state.vectorContentRules.vectorState;
|
||||
|
@ -434,7 +433,7 @@ module.exports = createReactClass({
|
|||
}
|
||||
}
|
||||
|
||||
Promise.all(deferreds).done(function(resps) {
|
||||
Promise.all(deferreds).then(function(resps) {
|
||||
self._refreshFromServer();
|
||||
}, onError);
|
||||
}, onError);
|
||||
|
@ -650,7 +649,7 @@ module.exports = createReactClass({
|
|||
externalContentRules: self.state.externalContentRules,
|
||||
externalPushRules: self.state.externalPushRules,
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
|
||||
MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids}));
|
||||
},
|
||||
|
|
|
@ -16,13 +16,9 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import sdk from '../../../index';
|
||||
import Field from "../elements/Field";
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import {SERVICE_TYPES} from "matrix-js-sdk";
|
||||
import {IntegrationManagerInstance} from "../../../integrations/IntegrationManagerInstance";
|
||||
import Modal from "../../../Modal";
|
||||
import sdk from '../../../index';
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
|
||||
export default class SetIntegrationManager extends React.Component {
|
||||
constructor() {
|
||||
|
@ -32,135 +28,23 @@ export default class SetIntegrationManager extends React.Component {
|
|||
|
||||
this.state = {
|
||||
currentManager,
|
||||
url: "", // user-entered text
|
||||
error: null,
|
||||
busy: false,
|
||||
checking: false,
|
||||
provisioningEnabled: SettingsStore.getValue("integrationProvisioning"),
|
||||
};
|
||||
}
|
||||
|
||||
_onUrlChanged = (ev) => {
|
||||
const u = ev.target.value;
|
||||
this.setState({url: u});
|
||||
};
|
||||
onProvisioningToggled = () => {
|
||||
const current = this.state.provisioningEnabled;
|
||||
SettingsStore.setValue("integrationProvisioning", null, SettingLevel.ACCOUNT, !current).catch(err => {
|
||||
console.error("Error changing integration manager provisioning");
|
||||
console.error(err);
|
||||
|
||||
_getTooltip = () => {
|
||||
if (this.state.checking) {
|
||||
const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner');
|
||||
return <div>
|
||||
<InlineSpinner />
|
||||
{ _t("Checking server") }
|
||||
</div>;
|
||||
} else if (this.state.error) {
|
||||
return <span className="warning">{this.state.error}</span>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
_canChange = () => {
|
||||
return !!this.state.url && !this.state.busy;
|
||||
};
|
||||
|
||||
_continueTerms = async (manager) => {
|
||||
try {
|
||||
await IntegrationManagers.sharedInstance().overwriteManagerOnAccount(manager);
|
||||
this.setState({
|
||||
busy: false,
|
||||
error: null,
|
||||
currentManager: IntegrationManagers.sharedInstance().getPrimaryManager(),
|
||||
url: "", // clear input
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.setState({
|
||||
busy: false,
|
||||
error: _t("Failed to update integration manager"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_setManager = async (ev) => {
|
||||
// Don't reload the page when the user hits enter in the form.
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
this.setState({busy: true, checking: true, error: null});
|
||||
|
||||
let offline = false;
|
||||
let manager: IntegrationManagerInstance;
|
||||
try {
|
||||
manager = await IntegrationManagers.sharedInstance().tryDiscoverManager(this.state.url);
|
||||
offline = !manager; // no manager implies offline
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
offline = true; // probably a connection error
|
||||
}
|
||||
if (offline) {
|
||||
this.setState({
|
||||
busy: false,
|
||||
checking: false,
|
||||
error: _t("Integration manager offline or not accessible."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Test the manager (causes terms of service prompt if agreement is needed)
|
||||
// We also cancel the tooltip at this point so it doesn't collide with the dialog.
|
||||
this.setState({checking: false});
|
||||
try {
|
||||
const client = manager.getScalarClient();
|
||||
await client.connect();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.setState({
|
||||
busy: false,
|
||||
error: _t("Terms of service not accepted or the integration manager is invalid."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Specifically request the terms of service to see if there are any.
|
||||
// The above won't trigger a terms of service check if there are no terms to
|
||||
// sign, so when there's no terms at all we need to ensure we tell the user.
|
||||
let hasTerms = true;
|
||||
try {
|
||||
const terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IM, manager.trimmedApiUrl);
|
||||
hasTerms = terms && terms['policies'] && Object.keys(terms['policies']).length > 0;
|
||||
} catch (e) {
|
||||
// Assume errors mean there are no terms. This could be a 404, 500, etc
|
||||
console.error(e);
|
||||
hasTerms = false;
|
||||
}
|
||||
if (!hasTerms) {
|
||||
this.setState({busy: false});
|
||||
const QuestionDialog = sdk.getComponent("views.dialogs.QuestionDialog");
|
||||
Modal.createTrackedDialog('No Terms Warning', '', QuestionDialog, {
|
||||
title: _t("Integration manager has no terms of service"),
|
||||
description: (
|
||||
<div>
|
||||
<span className="warning">
|
||||
{_t("The integration manager you have chosen does not have any terms of service.")}
|
||||
</span>
|
||||
<span>
|
||||
{_t("Only continue if you trust the owner of the server.")}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
button: _t("Continue"),
|
||||
onFinished: async (confirmed) => {
|
||||
if (!confirmed) return;
|
||||
this._continueTerms(manager);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._continueTerms(manager);
|
||||
this.setState({provisioningEnabled: current});
|
||||
});
|
||||
this.setState({provisioningEnabled: !current});
|
||||
};
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
|
||||
const ToggleSwitch = sdk.getComponent("views.elements.ToggleSwitch");
|
||||
|
||||
const currentManager = this.state.currentManager;
|
||||
let managerName;
|
||||
|
@ -168,45 +52,32 @@ export default class SetIntegrationManager extends React.Component {
|
|||
if (currentManager) {
|
||||
managerName = `(${currentManager.name})`;
|
||||
bodyText = _t(
|
||||
"You are currently using <b>%(serverName)s</b> to manage your bots, widgets, " +
|
||||
"Use an Integration Manager <b>(%(serverName)s)</b> to manage bots, widgets, " +
|
||||
"and sticker packs.",
|
||||
{serverName: currentManager.name},
|
||||
{ b: sub => <b>{sub}</b> },
|
||||
);
|
||||
} else {
|
||||
bodyText = _t(
|
||||
"Add which integration manager you want to manage your bots, widgets, " +
|
||||
"and sticker packs.",
|
||||
);
|
||||
bodyText = _t("Use an Integration Manager to manage bots, widgets, and sticker packs.");
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="mx_SettingsTab_section mx_SetIntegrationManager" onSubmit={this._setManager}>
|
||||
<div className='mx_SetIntegrationManager'>
|
||||
<div className="mx_SettingsTab_heading">
|
||||
<span>{_t("Integration Manager")}</span>
|
||||
<span>{_t("Manage integrations")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{managerName}</span>
|
||||
<ToggleSwitch checked={this.state.provisioningEnabled} onChange={this.onProvisioningToggled} />
|
||||
</div>
|
||||
<span className="mx_SettingsTab_subsectionText">
|
||||
{bodyText}
|
||||
<br />
|
||||
<br />
|
||||
{_t(
|
||||
"Integration Managers receive configuration data, and can modify widgets, " +
|
||||
"send room invites, and set power levels on your behalf.",
|
||||
)}
|
||||
</span>
|
||||
<Field
|
||||
label={_t("Enter a new integration manager")}
|
||||
id="mx_SetIntegrationManager_newUrl"
|
||||
type="text" value={this.state.url}
|
||||
autoComplete="off"
|
||||
onChange={this._onUrlChanged}
|
||||
tooltipContent={this._getTooltip()}
|
||||
tooltipClassName="mx_SetIntegrationManager_tooltip"
|
||||
disabled={this.state.busy}
|
||||
flagInvalid={!!this.state.error}
|
||||
/>
|
||||
<AccessibleButton
|
||||
kind="primary_sm"
|
||||
type="submit"
|
||||
disabled={!this._canChange()}
|
||||
onClick={this._setManager}
|
||||
>{_t("Change")}</AccessibleButton>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ export default class HelpUserSettingsTab extends React.Component {
|
|||
// stopping in the middle of the logs.
|
||||
console.log("Clear cache & reload clicked");
|
||||
MatrixClientPeg.get().stopClient();
|
||||
MatrixClientPeg.get().store.deleteAllData().done(() => {
|
||||
MatrixClientPeg.get().store.deleteAllData().then(() => {
|
||||
PlatformPeg.get().reload();
|
||||
});
|
||||
};
|
||||
|
|
123
src/components/views/toasts/VerificationRequestToast.js
Normal file
123
src/components/views/toasts/VerificationRequestToast.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 Modal from "../../../Modal";
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
|
||||
import KeyVerificationStateObserver, {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver";
|
||||
import dis from "../../../dispatcher";
|
||||
|
||||
export default class VerificationRequestToast extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const {event, timeout} = props.request;
|
||||
// to_device requests don't have a timestamp, so consider them age=0
|
||||
const age = event.getTs() ? event.getLocalAge() : 0;
|
||||
const remaining = Math.max(0, timeout - age);
|
||||
const counter = Math.ceil(remaining / 1000);
|
||||
this.state = {counter};
|
||||
if (this.props.requestObserver) {
|
||||
this.props.requestObserver.setCallback(this._checkRequestIsPending);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.requestObserver) {
|
||||
this.props.requestObserver.attach();
|
||||
this._checkRequestIsPending();
|
||||
}
|
||||
this._intervalHandle = setInterval(() => {
|
||||
let {counter} = this.state;
|
||||
counter -= 1;
|
||||
if (counter <= 0) {
|
||||
this.cancel();
|
||||
} else {
|
||||
this.setState({counter});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this._intervalHandle);
|
||||
if (this.props.requestObserver) {
|
||||
this.props.requestObserver.detach();
|
||||
}
|
||||
}
|
||||
|
||||
_checkRequestIsPending = () => {
|
||||
if (!this.props.requestObserver.pending) {
|
||||
this.props.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
cancel = () => {
|
||||
this.props.dismiss();
|
||||
try {
|
||||
this.props.request.cancel();
|
||||
} catch (err) {
|
||||
console.error("Error while cancelling verification request", err);
|
||||
}
|
||||
}
|
||||
|
||||
accept = () => {
|
||||
this.props.dismiss();
|
||||
const {event} = this.props.request;
|
||||
// no room id for to_device requests
|
||||
if (event.getRoomId()) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: event.getRoomId(),
|
||||
should_peek: false,
|
||||
});
|
||||
}
|
||||
|
||||
const verifier = this.props.request.beginKeyVerification(verificationMethods.SAS);
|
||||
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
|
||||
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {verifier});
|
||||
};
|
||||
|
||||
render() {
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
const {event} = this.props.request;
|
||||
const userId = event.getSender();
|
||||
let nameLabel = event.getRoomId() ? userLabelForEventRoom(userId, event) : userId;
|
||||
// for legacy to_device verification requests
|
||||
if (nameLabel === userId) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const user = client.getUser(event.getSender());
|
||||
if (user && user.displayName) {
|
||||
nameLabel = _t("%(name)s (%(userId)s)", {name: user.displayName, userId});
|
||||
}
|
||||
}
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">{nameLabel}</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={_t("Decline (%(counter)s)", {counter: this.state.counter})} kind="danger" onClick={this.cancel} />
|
||||
<FormButton label={_t("Accept")} onClick={this.accept} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
VerificationRequestToast.propTypes = {
|
||||
dismiss: PropTypes.func.isRequired,
|
||||
request: PropTypes.object.isRequired,
|
||||
requestObserver: PropTypes.instanceOf(KeyVerificationStateObserver),
|
||||
};
|
|
@ -90,6 +90,13 @@ module.exports = createReactClass({
|
|||
}
|
||||
} else {
|
||||
call = CallHandler.getAnyActiveCall();
|
||||
// Ignore calls if we can't get the room associated with them.
|
||||
// I think the underlying problem is that the js-sdk sends events
|
||||
// for calls before it has made the rooms available in the store,
|
||||
// although this isn't confirmed.
|
||||
if (MatrixClientPeg.get().getRoom(call.roomId) === null) {
|
||||
call = null;
|
||||
}
|
||||
this.setState({ call: call });
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue