Merge branch 'develop' into foldleft/better-errors
This commit is contained in:
commit
5ef06357f6
307 changed files with 2986 additions and 1615 deletions
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2020 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.
|
||||
|
@ -16,93 +17,10 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
|
||||
// derived from code from github.com/noeldelgado/gemini-scrollbar
|
||||
// Copyright (c) Noel Delgado <pixelia.me@gmail.com> (pixelia.me)
|
||||
function getScrollbarWidth(alternativeOverflow) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'mx_AutoHideScrollbar'; //to get width of css scrollbar
|
||||
div.style.position = 'absolute';
|
||||
div.style.top = '-9999px';
|
||||
div.style.width = '100px';
|
||||
div.style.height = '100px';
|
||||
div.style.overflow = "scroll";
|
||||
if (alternativeOverflow) {
|
||||
div.style.overflow = alternativeOverflow;
|
||||
}
|
||||
div.style.msOverflowStyle = '-ms-autohiding-scrollbar';
|
||||
document.body.appendChild(div);
|
||||
const scrollbarWidth = (div.offsetWidth - div.clientWidth);
|
||||
document.body.removeChild(div);
|
||||
return scrollbarWidth;
|
||||
}
|
||||
|
||||
function install() {
|
||||
const scrollbarWidth = getScrollbarWidth();
|
||||
if (scrollbarWidth !== 0) {
|
||||
const hasForcedOverlayScrollbar = getScrollbarWidth('overlay') === 0;
|
||||
// overflow: overlay on webkit doesn't auto hide the scrollbar
|
||||
if (hasForcedOverlayScrollbar) {
|
||||
document.body.classList.add("mx_scrollbar_overlay_noautohide");
|
||||
} else {
|
||||
document.body.classList.add("mx_scrollbar_nooverlay");
|
||||
const style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
style.innerText =
|
||||
`body.mx_scrollbar_nooverlay { --scrollbar-width: ${scrollbarWidth}px; }`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const installBodyClassesIfNeeded = (function() {
|
||||
let installed = false;
|
||||
return function() {
|
||||
if (!installed) {
|
||||
install();
|
||||
installed = true;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
export default class AutoHideScrollbar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onOverflow = this.onOverflow.bind(this);
|
||||
this.onUnderflow = this.onUnderflow.bind(this);
|
||||
this._collectContainerRef = this._collectContainerRef.bind(this);
|
||||
this._needsOverflowListener = null;
|
||||
}
|
||||
|
||||
onOverflow() {
|
||||
this.containerRef.classList.add("mx_AutoHideScrollbar_overflow");
|
||||
this.containerRef.classList.remove("mx_AutoHideScrollbar_underflow");
|
||||
}
|
||||
|
||||
onUnderflow() {
|
||||
this.containerRef.classList.remove("mx_AutoHideScrollbar_overflow");
|
||||
this.containerRef.classList.add("mx_AutoHideScrollbar_underflow");
|
||||
}
|
||||
|
||||
checkOverflow() {
|
||||
if (!this._needsOverflowListener) {
|
||||
return;
|
||||
}
|
||||
if (this.containerRef.scrollHeight > this.containerRef.clientHeight) {
|
||||
this.onOverflow();
|
||||
} else {
|
||||
this.onUnderflow();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.checkOverflow();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
installBodyClassesIfNeeded();
|
||||
this._needsOverflowListener =
|
||||
document.body.classList.contains("mx_scrollbar_nooverlay");
|
||||
this.checkOverflow();
|
||||
}
|
||||
|
||||
_collectContainerRef(ref) {
|
||||
|
@ -126,9 +44,7 @@ export default class AutoHideScrollbar extends React.Component {
|
|||
onScroll={this.props.onScroll}
|
||||
onWheel={this.props.onWheel}
|
||||
>
|
||||
<div className="mx_AutoHideScrollbar_offset">
|
||||
{ this.props.children }
|
||||
</div>
|
||||
{ this.props.children }
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ class CustomRoomTagPanel extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
this._tagStoreToken = CustomRoomTagStore.addListener(() => {
|
||||
this.setState({tags: CustomRoomTagStore.getSortedTags()});
|
||||
});
|
||||
|
|
|
@ -37,6 +37,8 @@ export default class EmbeddedPage extends React.PureComponent {
|
|||
className: PropTypes.string,
|
||||
// Whether to wrap the page in a scrollbar
|
||||
scrollbar: PropTypes.bool,
|
||||
// Map of keys to replace with values, e.g {$placeholder: "value"}
|
||||
replaceMap: PropTypes.object,
|
||||
};
|
||||
|
||||
static contextType = MatrixClientContext;
|
||||
|
@ -56,7 +58,7 @@ export default class EmbeddedPage extends React.PureComponent {
|
|||
return sanitizeHtml(_t(s));
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
this._unmounted = false;
|
||||
|
||||
if (!this.props.url) {
|
||||
|
@ -81,6 +83,13 @@ export default class EmbeddedPage extends React.PureComponent {
|
|||
}
|
||||
|
||||
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
|
||||
|
||||
if (this.props.replaceMap) {
|
||||
Object.keys(this.props.replaceMap).forEach(key => {
|
||||
body = body.split(key).join(this.props.replaceMap[key]);
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ page: body });
|
||||
},
|
||||
);
|
||||
|
|
|
@ -428,12 +428,11 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this._unmounted = false;
|
||||
this._matrixClient = MatrixClientPeg.get();
|
||||
this._matrixClient.on("Group.myMembership", this._onGroupMyMembership);
|
||||
|
||||
this._changeAvatarComponent = null;
|
||||
this._initGroupStore(this.props.groupId, true);
|
||||
|
||||
this._dispatcherRef = dis.register(this._onAction);
|
||||
|
@ -451,8 +450,9 @@ export default createReactClass({
|
|||
}
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
if (this.props.groupId != newProps.groupId) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps: function(newProps) {
|
||||
if (this.props.groupId !== newProps.groupId) {
|
||||
this.setState({
|
||||
summary: null,
|
||||
error: null,
|
||||
|
|
|
@ -66,6 +66,22 @@ export default class IndicatorScrollbar extends React.Component {
|
|||
this._autoHideScrollbar = autoHideScrollbar;
|
||||
}
|
||||
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const prevLen = prevProps && prevProps.children && prevProps.children.length || 0;
|
||||
const curLen = this.props.children && this.props.children.length || 0;
|
||||
// check overflow only if amount of children changes.
|
||||
// if we don't guard here, we end up with an infinite
|
||||
// render > componentDidUpdate > checkOverflow > setState > render loop
|
||||
if (prevLen !== curLen) {
|
||||
this.checkOverflow();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.checkOverflow();
|
||||
}
|
||||
|
||||
checkOverflow() {
|
||||
const hasTopOverflow = this._scrollElement.scrollTop > 0;
|
||||
const hasBottomOverflow = this._scrollElement.scrollHeight >
|
||||
|
@ -95,10 +111,6 @@ export default class IndicatorScrollbar extends React.Component {
|
|||
this._scrollElement.classList.remove("mx_IndicatorScrollbar_rightOverflow");
|
||||
}
|
||||
|
||||
if (this._autoHideScrollbar) {
|
||||
this._autoHideScrollbar.checkOverflow();
|
||||
}
|
||||
|
||||
if (this.props.trackHorizontalOverflow) {
|
||||
this.setState({
|
||||
// Offset from absolute position of the container
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
Copyright 2017 Vector Creations Ltd.
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019, 2020 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.
|
||||
|
@ -24,6 +24,8 @@ import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryCom
|
|||
|
||||
import * as sdk from '../../index';
|
||||
|
||||
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'InteractiveAuth',
|
||||
|
||||
|
@ -47,7 +49,7 @@ export default createReactClass({
|
|||
// @param {bool} status True if the operation requiring
|
||||
// auth was completed sucessfully, false if canceled.
|
||||
// @param {object} result The result of the authenticated call
|
||||
// if successful, otherwise the error object
|
||||
// if successful, otherwise the error object.
|
||||
// @param {object} extra Additional information about the UI Auth
|
||||
// process:
|
||||
// * emailSid {string} If email auth was performed, the sid of
|
||||
|
@ -75,6 +77,15 @@ export default createReactClass({
|
|||
// is managed by some other party and should not be managed by
|
||||
// the component itself.
|
||||
continueIsManaged: PropTypes.bool,
|
||||
|
||||
// Called when the stage changes, or the stage's phase changes. First
|
||||
// argument is the stage, second is the phase. Some stages do not have
|
||||
// phases and will be counted as 0 (numeric).
|
||||
onStagePhaseChange: PropTypes.func,
|
||||
|
||||
// continueText and continueKind are passed straight through to the AuthEntryComponent.
|
||||
continueText: PropTypes.string,
|
||||
continueKind: PropTypes.string,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -87,7 +98,8 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._unmounted = false;
|
||||
this._authLogic = new InteractiveAuth({
|
||||
authData: this.props.authData,
|
||||
|
@ -204,6 +216,16 @@ export default createReactClass({
|
|||
this._authLogic.submitAuthDict(authData);
|
||||
},
|
||||
|
||||
_onPhaseChange: function(newPhase) {
|
||||
if (this.props.onStagePhaseChange) {
|
||||
this.props.onStagePhaseChange(this.state.authStage, newPhase || 0);
|
||||
}
|
||||
},
|
||||
|
||||
_onStageCancel: function() {
|
||||
this.props.onAuthFinished(false, ERROR_USER_CANCELLED);
|
||||
},
|
||||
|
||||
_renderCurrentStage: function() {
|
||||
const stage = this.state.authStage;
|
||||
if (!stage) {
|
||||
|
@ -232,6 +254,10 @@ export default createReactClass({
|
|||
fail={this._onAuthStageFailed}
|
||||
setEmailSid={this._setEmailSid}
|
||||
showContinue={!this.props.continueIsManaged}
|
||||
onPhaseChange={this._onPhaseChange}
|
||||
continueText={this.props.continueText}
|
||||
continueKind={this.props.continueKind}
|
||||
onCancel={this._onStageCancel}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -44,7 +44,8 @@ const LeftPanel = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO: [REACT-WARNING] Move this to constructor
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this.focusedElement = null;
|
||||
|
||||
this._breadcrumbsWatcherRef = SettingsStore.watchSetting(
|
||||
|
|
|
@ -22,7 +22,7 @@ import createReactClass from 'create-react-class';
|
|||
import PropTypes from 'prop-types';
|
||||
import { DragDropContext } from 'react-beautiful-dnd';
|
||||
|
||||
import { Key, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
|
||||
import {Key, isOnlyCtrlOrCmdKeyEvent, isOnlyCtrlOrCmdIgnoreShiftKeyEvent} from '../../Keyboard';
|
||||
import PageTypes from '../../PageTypes';
|
||||
import CallMediaHandler from '../../CallMediaHandler';
|
||||
import { fixupColorFonts } from '../../utils/FontManager';
|
||||
|
@ -94,7 +94,8 @@ const LoggedInView = createReactClass({
|
|||
this._loadResizerPreferences();
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
// stash the MatrixClient in case we log out before we are unmounted
|
||||
this._matrixClient = this.props.matrixClient;
|
||||
|
||||
|
@ -380,7 +381,7 @@ const LoggedInView = createReactClass({
|
|||
break;
|
||||
|
||||
case Key.SLASH:
|
||||
if (ctrlCmdOnly) {
|
||||
if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev)) {
|
||||
KeyboardShortcuts.toggleDialog();
|
||||
handled = true;
|
||||
}
|
||||
|
|
|
@ -221,7 +221,8 @@ export default createReactClass({
|
|||
return {serverConfig: props};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO: [REACT-WARNING] Move this to constructor
|
||||
UNSAFE_componentWillMount: function() {
|
||||
SdkConfig.put(this.props.config);
|
||||
|
||||
// Used by _viewRoom before getting state from sync
|
||||
|
@ -261,9 +262,7 @@ export default createReactClass({
|
|||
|
||||
this._accountPassword = null;
|
||||
this._accountPasswordTimer = null;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this._themeWatcher = new ThemeWatcher();
|
||||
this._themeWatcher.start();
|
||||
|
@ -361,7 +360,8 @@ export default createReactClass({
|
|||
if (this._accountPasswordTimer !== null) clearTimeout(this._accountPasswordTimer);
|
||||
},
|
||||
|
||||
componentWillUpdate: function(props, state) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle stage
|
||||
UNSAFE_componentWillUpdate: function(props, state) {
|
||||
if (this.shouldTrackPageChange(this.state, state)) {
|
||||
this.startPageChangeTimer();
|
||||
}
|
||||
|
@ -382,7 +382,7 @@ export default createReactClass({
|
|||
// Tor doesn't support performance
|
||||
if (!performance || !performance.mark) return null;
|
||||
|
||||
// This shouldn't happen because componentWillUpdate and componentDidUpdate
|
||||
// This shouldn't happen because UNSAFE_componentWillUpdate and componentDidUpdate
|
||||
// are used.
|
||||
if (this._pageChanging) {
|
||||
console.warn('MatrixChat.startPageChangeTimer: timer already started');
|
||||
|
@ -657,6 +657,7 @@ export default createReactClass({
|
|||
collapseLhs: true,
|
||||
});
|
||||
break;
|
||||
case 'focus_room_filter': // for CtrlOrCmd+K to work by expanding the left panel first
|
||||
case 'show_left_panel':
|
||||
this.setState({
|
||||
collapseLhs: false,
|
||||
|
@ -1520,7 +1521,7 @@ export default createReactClass({
|
|||
} else if (request.pending) {
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: 'verifreq_' + request.channel.transactionId,
|
||||
title: _t("Verification Request"),
|
||||
title: request.isSelfVerification ? _t("Self-verification request") : _t("Verification Request"),
|
||||
icon: "verification",
|
||||
props: {request},
|
||||
component: sdk.getComponent("toasts.VerificationRequestToast"),
|
||||
|
@ -2021,7 +2022,7 @@ export default createReactClass({
|
|||
}
|
||||
} else if (this.state.view === VIEWS.WELCOME) {
|
||||
const Welcome = sdk.getComponent('auth.Welcome');
|
||||
view = <Welcome />;
|
||||
view = <Welcome {...this.getServerProperties()} />;
|
||||
} else if (this.state.view === VIEWS.REGISTER) {
|
||||
const Registration = sdk.getComponent('structures.auth.Registration');
|
||||
view = (
|
||||
|
|
|
@ -844,14 +844,16 @@ class CreationGrouper {
|
|||
// events that we include in the group but then eject out and place
|
||||
// above the group.
|
||||
this.ejectedEvents = [];
|
||||
this.readMarker = panel._readMarkerForEvent(createEvent.getId());
|
||||
this.readMarker = panel._readMarkerForEvent(
|
||||
createEvent.getId(),
|
||||
createEvent === lastShownEvent,
|
||||
);
|
||||
}
|
||||
|
||||
shouldGroup(ev) {
|
||||
const panel = this.panel;
|
||||
const createEvent = this.createEvent;
|
||||
if (!panel._shouldShowEvent(ev)) {
|
||||
this.readMarker = this.readMarker || panel._readMarkerForEvent(ev.getId());
|
||||
return true;
|
||||
}
|
||||
if (panel._wantsDateSeparator(this.createEvent, ev.getDate())) {
|
||||
|
@ -869,7 +871,10 @@ class CreationGrouper {
|
|||
|
||||
add(ev) {
|
||||
const panel = this.panel;
|
||||
this.readMarker = this.readMarker || panel._readMarkerForEvent(ev.getId());
|
||||
this.readMarker = this.readMarker || panel._readMarkerForEvent(
|
||||
ev.getId(),
|
||||
ev === this.lastShownEvent,
|
||||
);
|
||||
if (!panel._shouldShowEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
|
@ -956,7 +961,10 @@ class MemberGrouper {
|
|||
|
||||
constructor(panel, ev, prevEvent, lastShownEvent) {
|
||||
this.panel = panel;
|
||||
this.readMarker = panel._readMarkerForEvent(ev.getId());
|
||||
this.readMarker = panel._readMarkerForEvent(
|
||||
ev.getId(),
|
||||
ev === lastShownEvent,
|
||||
);
|
||||
this.events = [ev];
|
||||
this.prevEvent = prevEvent;
|
||||
this.lastShownEvent = lastShownEvent;
|
||||
|
@ -977,7 +985,10 @@ class MemberGrouper {
|
|||
const renderText = textForEvent(ev);
|
||||
if (!renderText || renderText.trim().length === 0) return; // quietly ignore
|
||||
}
|
||||
this.readMarker = this.readMarker || this.panel._readMarkerForEvent(ev.getId());
|
||||
this.readMarker = this.readMarker || this.panel._readMarkerForEvent(
|
||||
ev.getId(),
|
||||
ev === this.lastShownEvent,
|
||||
);
|
||||
this.events.push(ev);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ export default createReactClass({
|
|||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this._fetch();
|
||||
},
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ export default class RightPanel extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
const cli = this.context;
|
||||
cli.on("RoomState.members", this.onRoomStateMember);
|
||||
|
@ -123,7 +123,8 @@ export default class RightPanel extends React.Component {
|
|||
this._unregisterGroupStore(this.props.groupId);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase
|
||||
if (newProps.groupId !== this.props.groupId) {
|
||||
this._unregisterGroupStore(this.props.groupId);
|
||||
this._initGroupStore(newProps.groupId);
|
||||
|
|
|
@ -56,7 +56,8 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO: [REACT-WARNING] Move this to constructor
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._unmounted = false;
|
||||
this.nextBatch = null;
|
||||
this.filterTimeout = null;
|
||||
|
@ -89,9 +90,7 @@ export default createReactClass({
|
|||
),
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.refreshRoomList();
|
||||
},
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
|
||||
MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ export default class RoomSubList extends React.PureComponent {
|
|||
break;
|
||||
|
||||
case 'view_room':
|
||||
if (this.state.hidden && !this.props.forceExpand &&
|
||||
if (this.state.hidden && !this.props.forceExpand && payload.show_room_tile &&
|
||||
this.props.list.some((r) => r.roomId === payload.room_id)
|
||||
) {
|
||||
this.toggle();
|
||||
|
@ -193,6 +193,7 @@ export default class RoomSubList extends React.PureComponent {
|
|||
onRoomTileClick = (roomId, ev) => {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
show_room_tile: true, // to make sure the room gets scrolled into view
|
||||
room_id: roomId,
|
||||
clear_search: (ev && (ev.key === Key.ENTER || ev.key === Key.SPACE)),
|
||||
});
|
||||
|
|
|
@ -55,6 +55,7 @@ import RightPanelStore from "../../stores/RightPanelStore";
|
|||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
||||
import RoomContext from "../../contexts/RoomContext";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import { shieldStatusForRoom } from '../../utils/ShieldUtils';
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function() {};
|
||||
|
@ -167,7 +168,8 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this.context.on("Room", this.onRoom);
|
||||
this.context.on("Room.timeline", this.onRoomTimeline);
|
||||
|
@ -235,6 +237,11 @@ export default createReactClass({
|
|||
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
|
||||
};
|
||||
|
||||
if (!initial && this.state.shouldPeek && !newState.shouldPeek) {
|
||||
// Stop peeking because we have joined this room now
|
||||
this.context.stopPeeking();
|
||||
}
|
||||
|
||||
// Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307
|
||||
console.log(
|
||||
'RVS update:',
|
||||
|
@ -466,6 +473,10 @@ export default createReactClass({
|
|||
RoomScrollStateStore.setScrollState(this.state.roomId, this._getScrollState());
|
||||
}
|
||||
|
||||
if (this.state.shouldPeek) {
|
||||
this.context.stopPeeking();
|
||||
}
|
||||
|
||||
// stop tracking room changes to format permalinks
|
||||
this._stopAllPermalinkCreators();
|
||||
|
||||
|
@ -817,40 +828,9 @@ export default createReactClass({
|
|||
return;
|
||||
}
|
||||
|
||||
// Duplication between here and _updateE2eStatus in RoomTile
|
||||
/* At this point, the user has encryption on and cross-signing on */
|
||||
const e2eMembers = await room.getEncryptionTargetMembers();
|
||||
const verified = [];
|
||||
const unverified = [];
|
||||
e2eMembers.map(({userId}) => userId)
|
||||
.filter((userId) => userId !== this.context.getUserId())
|
||||
.forEach((userId) => {
|
||||
(this.context.checkUserTrust(userId).isCrossSigningVerified() ?
|
||||
verified : unverified).push(userId);
|
||||
});
|
||||
|
||||
debuglog("e2e verified", verified, "unverified", unverified);
|
||||
|
||||
/* Check all verified user devices. */
|
||||
/* Don't alarm if no other users are verified */
|
||||
const targets = (verified.length > 0) ? [...verified, this.context.getUserId()] : verified;
|
||||
for (const userId of targets) {
|
||||
const devices = await this.context.getStoredDevicesForUser(userId);
|
||||
const anyDeviceNotVerified = devices.some(({deviceId}) => {
|
||||
return !this.context.checkDeviceTrust(userId, deviceId).isVerified();
|
||||
});
|
||||
if (anyDeviceNotVerified) {
|
||||
this.setState({
|
||||
e2eStatus: "warning",
|
||||
});
|
||||
debuglog("e2e status set to warning as not all users trust all of their sessions." +
|
||||
" Aborted on user", userId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
e2eStatus: unverified.length === 0 ? "verified" : "normal",
|
||||
e2eStatus: await shieldStatusForRoom(this.context, room),
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -156,9 +156,8 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._fillRequestWhileRunning = false;
|
||||
this._isFilling = false;
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._pendingFillRequests = {b: null, f: null};
|
||||
|
||||
if (this.props.resizeNotifier) {
|
||||
|
|
|
@ -53,6 +53,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._search = createRef();
|
||||
},
|
||||
|
|
|
@ -20,6 +20,7 @@ import * as React from "react";
|
|||
import {_t} from '../../languageHandler';
|
||||
import * as PropTypes from "prop-types";
|
||||
import * as sdk from "../../index";
|
||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||
import { ReactNode } from "react";
|
||||
|
||||
/**
|
||||
|
@ -113,9 +114,9 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
|||
private _renderTabPanel(tab: Tab): React.ReactNode {
|
||||
return (
|
||||
<div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}>
|
||||
<div className='mx_TabbedView_tabPanelContent'>
|
||||
<AutoHideScrollbar className='mx_TabbedView_tabPanelContent'>
|
||||
{tab.body}
|
||||
</div>
|
||||
</AutoHideScrollbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ const TagPanel = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this.unmounted = false;
|
||||
this.context.on("Group.myMembership", this._onGroupMyMembership);
|
||||
this.context.on("sync", this._onClientSync);
|
||||
|
|
|
@ -202,7 +202,8 @@ const TimelinePanel = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
debuglog("TimelinePanel: mounting");
|
||||
|
||||
this.lastRRSentEventId = undefined;
|
||||
|
@ -234,7 +235,8 @@ const TimelinePanel = createReactClass({
|
|||
this._initTimeline(this.props);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps: function(newProps) {
|
||||
if (newProps.timelineSet !== this.props.timelineSet) {
|
||||
// throw new Error("changing timelineSet on a TimelinePanel is not supported");
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export default class UserView extends React.Component {
|
|||
this.state = {};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
if (this.props.userId) {
|
||||
this._loadProfileInfo();
|
||||
}
|
||||
|
|
|
@ -69,12 +69,13 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this.reset = null;
|
||||
this._checkServerLiveliness(this.props.serverConfig);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps: function(newProps) {
|
||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
||||
|
||||
|
@ -296,7 +297,6 @@ export default createReactClass({
|
|||
<form onSubmit={this.onSubmitForm}>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
<Field
|
||||
id="mx_ForgotPassword_email"
|
||||
name="reset_email" // define a name so browser's password autofill gets less confused
|
||||
type="text"
|
||||
label={_t('Email')}
|
||||
|
@ -307,7 +307,6 @@ export default createReactClass({
|
|||
</div>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
<Field
|
||||
id="mx_ForgotPassword_password"
|
||||
name="reset_password"
|
||||
type="password"
|
||||
label={_t('Password')}
|
||||
|
@ -315,7 +314,6 @@ export default createReactClass({
|
|||
onChange={this.onInputChanged.bind(this, "password")}
|
||||
/>
|
||||
<Field
|
||||
id="mx_ForgotPassword_passwordConfirm"
|
||||
name="reset_password_confirm"
|
||||
type="password"
|
||||
label={_t('Confirm')}
|
||||
|
|
|
@ -113,7 +113,8 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO: [REACT-WARNING] Move this to constructor
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._unmounted = false;
|
||||
|
||||
// map from login step type to a function which will render a control
|
||||
|
@ -133,7 +134,8 @@ export default createReactClass({
|
|||
this._unmounted = true;
|
||||
},
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
// There is some assymetry between ChangeDisplayName and ChangeAvatar,
|
||||
// as ChangeDisplayName will auto-get the name but ChangeAvatar expects
|
||||
// the URL to be passed to you (because it's also used for room avatars).
|
||||
|
|
|
@ -120,12 +120,13 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this._unmounted = false;
|
||||
this._replaceClient();
|
||||
},
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
||||
|
||||
|
@ -462,7 +463,7 @@ export default createReactClass({
|
|||
initial_device_display_name: this.props.defaultDeviceDisplayName,
|
||||
};
|
||||
if (auth) registerParams.auth = auth;
|
||||
if (inhibitLogin !== undefined && inhibitLogin !== null) registerParams.inhibitLogin = inhibitLogin;
|
||||
if (inhibitLogin !== undefined && inhibitLogin !== null) registerParams.inhibit_login = inhibitLogin;
|
||||
return this.state.matrixClient.registerRequest(registerParams);
|
||||
},
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ export default class SoftLogout extends React.Component {
|
|||
|
||||
this.state = {
|
||||
loginView: LOGIN_VIEW.LOADING,
|
||||
keyBackupNeeded: true, // assume we do while we figure it out (see componentWillMount)
|
||||
keyBackupNeeded: true, // assume we do while we figure it out (see componentDidMount)
|
||||
|
||||
busy: false,
|
||||
password: "",
|
||||
|
@ -213,7 +213,6 @@ export default class SoftLogout extends React.Component {
|
|||
<p>{introText}</p>
|
||||
{error}
|
||||
<Field
|
||||
id="softlogout_password"
|
||||
type="password"
|
||||
label={_t("Password")}
|
||||
onChange={this.onPasswordChange}
|
||||
|
|
|
@ -46,7 +46,8 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._captchaWidgetId = null;
|
||||
|
||||
this._recaptchaContainer = createRef();
|
||||
|
|
|
@ -60,7 +60,7 @@ export default class CountryDropdown extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
if (!this.props.value) {
|
||||
// If no value is given, we start with the default
|
||||
// country selected, but our parent component
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019, 2020 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.
|
||||
|
@ -25,6 +25,7 @@ import classnames from 'classnames';
|
|||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
|
||||
/* This file contains a collection of components which are used by the
|
||||
* InteractiveAuth to prompt the user to enter the information needed
|
||||
|
@ -59,11 +60,21 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
* session to be failed and the process to go back to the start.
|
||||
* setEmailSid: m.login.email.identity only: a function to be called with the
|
||||
* email sid after a token is requested.
|
||||
* onPhaseChange: A function which is called when the stage's phase changes. If
|
||||
* the stage has no phases, call this with DEFAULT_PHASE. Takes
|
||||
* one argument, the phase, and is always defined/required.
|
||||
* continueText: For stages which have a continue button, the text to use.
|
||||
* continueKind: For stages which have a continue button, the style of button to
|
||||
* use. For example, 'danger' or 'primary'.
|
||||
* onCancel A function with no arguments which is called by the stage if the
|
||||
* user knowingly cancelled/dismissed the authentication attempt.
|
||||
*
|
||||
* Each component may also provide the following functions (beyond the standard React ones):
|
||||
* focus: set the input focus appropriately in the form.
|
||||
*/
|
||||
|
||||
export const DEFAULT_PHASE = 0;
|
||||
|
||||
export const PasswordAuthEntry = createReactClass({
|
||||
displayName: 'PasswordAuthEntry',
|
||||
|
||||
|
@ -78,6 +89,11 @@ export const PasswordAuthEntry = createReactClass({
|
|||
// is the auth logic currently waiting for something to
|
||||
// happen?
|
||||
busy: PropTypes.bool,
|
||||
onPhaseChange: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -145,7 +161,6 @@ export const PasswordAuthEntry = createReactClass({
|
|||
<p>{ _t("Confirm your identity by entering your account password below.") }</p>
|
||||
<form onSubmit={this._onSubmit} className="mx_InteractiveAuthEntryComponents_passwordSection">
|
||||
<Field
|
||||
id="mx_InteractiveAuthEntryComponents_password"
|
||||
className={passwordBoxClass}
|
||||
type="password"
|
||||
name="passwordField"
|
||||
|
@ -176,6 +191,11 @@ export const RecaptchaAuthEntry = createReactClass({
|
|||
stageParams: PropTypes.object.isRequired,
|
||||
errorText: PropTypes.string,
|
||||
busy: PropTypes.bool,
|
||||
onPhaseChange: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||
},
|
||||
|
||||
_onCaptchaResponse: function(response) {
|
||||
|
@ -237,8 +257,14 @@ export const TermsAuthEntry = createReactClass({
|
|||
errorText: PropTypes.string,
|
||||
busy: PropTypes.bool,
|
||||
showContinue: PropTypes.bool,
|
||||
onPhaseChange: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Move this to constructor
|
||||
componentWillMount: function() {
|
||||
// example stageParams:
|
||||
//
|
||||
|
@ -379,6 +405,11 @@ export const EmailIdentityAuthEntry = createReactClass({
|
|||
stageState: PropTypes.object.isRequired,
|
||||
fail: PropTypes.func.isRequired,
|
||||
setEmailSid: PropTypes.func.isRequired,
|
||||
onPhaseChange: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -421,6 +452,7 @@ export const MsisdnAuthEntry = createReactClass({
|
|||
clientSecret: PropTypes.func,
|
||||
submitAuthDict: PropTypes.func.isRequired,
|
||||
matrixClient: PropTypes.object,
|
||||
onPhaseChange: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -430,7 +462,9 @@ export const MsisdnAuthEntry = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||
|
||||
this._submitUrl = null;
|
||||
this._sid = null;
|
||||
this._msisdn = null;
|
||||
|
@ -565,6 +599,91 @@ export const MsisdnAuthEntry = createReactClass({
|
|||
},
|
||||
});
|
||||
|
||||
export class SSOAuthEntry extends React.Component {
|
||||
static propTypes = {
|
||||
matrixClient: PropTypes.object.isRequired,
|
||||
authSessionId: PropTypes.string.isRequired,
|
||||
loginType: PropTypes.string.isRequired,
|
||||
submitAuthDict: PropTypes.func.isRequired,
|
||||
errorText: PropTypes.string,
|
||||
onPhaseChange: PropTypes.func.isRequired,
|
||||
continueText: PropTypes.string,
|
||||
continueKind: PropTypes.string,
|
||||
onCancel: PropTypes.func,
|
||||
};
|
||||
|
||||
static LOGIN_TYPE = "m.login.sso";
|
||||
static UNSTABLE_LOGIN_TYPE = "org.matrix.login.sso";
|
||||
|
||||
static PHASE_PREAUTH = 1; // button to start SSO
|
||||
static PHASE_POSTAUTH = 2; // button to confirm SSO completed
|
||||
|
||||
_ssoUrl: string;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// We actually send the user through fallback auth so we don't have to
|
||||
// deal with a redirect back to us, losing application context.
|
||||
this._ssoUrl = props.matrixClient.getFallbackAuthUrl(
|
||||
this.props.loginType,
|
||||
this.props.authSessionId,
|
||||
);
|
||||
|
||||
this.state = {
|
||||
phase: SSOAuthEntry.PHASE_PREAUTH,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.props.onPhaseChange(SSOAuthEntry.PHASE_PREAUTH);
|
||||
}
|
||||
|
||||
onStartAuthClick = () => {
|
||||
// Note: We don't use PlatformPeg's startSsoAuth functions because we almost
|
||||
// certainly will need to open the thing in a new tab to avoid losing application
|
||||
// context.
|
||||
|
||||
window.open(this._ssoUrl, '_blank');
|
||||
this.setState({phase: SSOAuthEntry.PHASE_POSTAUTH});
|
||||
this.props.onPhaseChange(SSOAuthEntry.PHASE_POSTAUTH);
|
||||
};
|
||||
|
||||
onConfirmClick = () => {
|
||||
this.props.submitAuthDict({});
|
||||
};
|
||||
|
||||
render() {
|
||||
let continueButton = null;
|
||||
const cancelButton = (
|
||||
<AccessibleButton
|
||||
onClick={this.props.onCancel}
|
||||
kind={this.props.continueKind ? (this.props.continueKind + '_outline') : 'primary_outline'}
|
||||
>{_t("Cancel")}</AccessibleButton>
|
||||
);
|
||||
if (this.state.phase === SSOAuthEntry.PHASE_PREAUTH) {
|
||||
continueButton = (
|
||||
<AccessibleButton
|
||||
onClick={this.onStartAuthClick}
|
||||
kind={this.props.continueKind || 'primary'}
|
||||
>{this.props.continueText || _t("Single Sign On")}</AccessibleButton>
|
||||
);
|
||||
} else {
|
||||
continueButton = (
|
||||
<AccessibleButton
|
||||
onClick={this.onConfirmClick}
|
||||
kind={this.props.continueKind || 'primary'}
|
||||
>{this.props.continueText || _t("Confirm")}</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className='mx_InteractiveAuthEntryComponents_sso_buttons'>
|
||||
{cancelButton}
|
||||
{continueButton}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export const FallbackAuthEntry = createReactClass({
|
||||
displayName: 'FallbackAuthEntry',
|
||||
|
||||
|
@ -574,9 +693,15 @@ export const FallbackAuthEntry = createReactClass({
|
|||
loginType: PropTypes.string.isRequired,
|
||||
submitAuthDict: PropTypes.func.isRequired,
|
||||
errorText: PropTypes.string,
|
||||
onPhaseChange: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
// we have to make the user click a button, as browsers will block
|
||||
// the popup if we open it immediately.
|
||||
this._popupWindow = null;
|
||||
|
@ -598,7 +723,10 @@ export const FallbackAuthEntry = createReactClass({
|
|||
}
|
||||
},
|
||||
|
||||
_onShowFallbackClick: function() {
|
||||
_onShowFallbackClick: function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const url = this.props.matrixClient.getFallbackAuthUrl(
|
||||
this.props.loginType,
|
||||
this.props.authSessionId,
|
||||
|
@ -627,7 +755,7 @@ export const FallbackAuthEntry = createReactClass({
|
|||
}
|
||||
return (
|
||||
<div>
|
||||
<a ref={this._fallbackButton} onClick={this._onShowFallbackClick}>{ _t("Start authentication") }</a>
|
||||
<a href="" ref={this._fallbackButton} onClick={this._onShowFallbackClick}>{ _t("Start authentication") }</a>
|
||||
{errorSection}
|
||||
</div>
|
||||
);
|
||||
|
@ -640,11 +768,12 @@ const AuthEntryComponents = [
|
|||
EmailIdentityAuthEntry,
|
||||
MsisdnAuthEntry,
|
||||
TermsAuthEntry,
|
||||
SSOAuthEntry,
|
||||
];
|
||||
|
||||
export default function getEntryComponentForLoginType(loginType) {
|
||||
for (const c of AuthEntryComponents) {
|
||||
if (c.LOGIN_TYPE == loginType) {
|
||||
if (c.LOGIN_TYPE === loginType || c.UNSTABLE_LOGIN_TYPE === loginType) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,7 +106,8 @@ export default class ModularServerConfig extends ServerConfig {
|
|||
)}
|
||||
<form onSubmit={this.onSubmit} autoComplete="off" action={null}>
|
||||
<div className="mx_ServerConfig_fields">
|
||||
<Field id="mx_ServerConfig_hsUrl"
|
||||
<Field
|
||||
id="mx_ServerConfig_hsUrl"
|
||||
label={_t("Server Name")}
|
||||
placeholder={this.props.serverConfig.hsUrl}
|
||||
value={this.state.hsUrl}
|
||||
|
|
|
@ -193,7 +193,6 @@ export default class PasswordLogin extends React.Component {
|
|||
classes.error = this.props.loginIncorrect && !this.state.username;
|
||||
return <Field
|
||||
className={classNames(classes)}
|
||||
id="mx_PasswordLogin_email"
|
||||
name="username" // make it a little easier for browser's remember-password
|
||||
key="email_input"
|
||||
type="text"
|
||||
|
@ -209,7 +208,6 @@ export default class PasswordLogin extends React.Component {
|
|||
classes.error = this.props.loginIncorrect && !this.state.username;
|
||||
return <Field
|
||||
className={classNames(classes)}
|
||||
id="mx_PasswordLogin_username"
|
||||
name="username" // make it a little easier for browser's remember-password
|
||||
key="username_input"
|
||||
type="text"
|
||||
|
@ -233,7 +231,6 @@ export default class PasswordLogin extends React.Component {
|
|||
|
||||
return <Field
|
||||
className={classNames(classes)}
|
||||
id="mx_PasswordLogin_phoneNumber"
|
||||
name="phoneNumber"
|
||||
key="phone_input"
|
||||
type="text"
|
||||
|
@ -290,7 +287,6 @@ export default class PasswordLogin extends React.Component {
|
|||
<div className="mx_Login_type_container">
|
||||
<label className="mx_Login_type_label">{ _t('Sign in with') }</label>
|
||||
<Field
|
||||
id="mx_PasswordLogin_type"
|
||||
element="select"
|
||||
value={this.state.loginType}
|
||||
onChange={this.onLoginTypeChange}
|
||||
|
@ -328,7 +324,6 @@ export default class PasswordLogin extends React.Component {
|
|||
{loginField}
|
||||
<Field
|
||||
className={pwFieldClass}
|
||||
id="mx_PasswordLogin_password"
|
||||
type="password"
|
||||
name="password"
|
||||
label={_t('Password')}
|
||||
|
|
|
@ -470,7 +470,6 @@ export default createReactClass({
|
|||
_t("Email") :
|
||||
_t("Email (optional)");
|
||||
return <Field
|
||||
id="mx_RegistrationForm_email"
|
||||
ref={field => this[FIELD_EMAIL] = field}
|
||||
type="text"
|
||||
label={emailPlaceholder}
|
||||
|
@ -524,7 +523,6 @@ export default createReactClass({
|
|||
onOptionChange={this.onPhoneCountryChange}
|
||||
/>;
|
||||
return <Field
|
||||
id="mx_RegistrationForm_phoneNumber"
|
||||
ref={field => this[FIELD_PHONE_NUMBER] = field}
|
||||
type="text"
|
||||
label={phoneLabel}
|
||||
|
|
|
@ -72,7 +72,8 @@ export default class ServerConfig extends React.PureComponent {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase
|
||||
if (newProps.serverConfig.hsUrl === this.state.hsUrl &&
|
||||
newProps.serverConfig.isUrl === this.state.isUrl) return;
|
||||
|
||||
|
@ -223,7 +224,8 @@ export default class ServerConfig extends React.PureComponent {
|
|||
{sub}
|
||||
</a>,
|
||||
})}
|
||||
<Field id="mx_ServerConfig_hsUrl"
|
||||
<Field
|
||||
id="mx_ServerConfig_hsUrl"
|
||||
label={_t("Homeserver URL")}
|
||||
placeholder={this.props.serverConfig.hsUrl}
|
||||
value={this.state.hsUrl}
|
||||
|
@ -246,7 +248,7 @@ export default class ServerConfig extends React.PureComponent {
|
|||
{sub}
|
||||
</a>,
|
||||
})}
|
||||
<Field id="mx_ServerConfig_isUrl"
|
||||
<Field
|
||||
label={_t("Identity Server URL")}
|
||||
placeholder={this.props.serverConfig.isUrl}
|
||||
value={this.state.isUrl || ''}
|
||||
|
|
|
@ -18,6 +18,12 @@ import React from 'react';
|
|||
import * as sdk from '../../../index';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import AuthPage from "./AuthPage";
|
||||
import * as Matrix from "matrix-js-sdk";
|
||||
import {_td} from "../../../languageHandler";
|
||||
import PlatformPeg from "../../../PlatformPeg";
|
||||
|
||||
// translatable strings for Welcome pages
|
||||
_td("Sign in with SSO");
|
||||
|
||||
export default class Welcome extends React.PureComponent {
|
||||
render() {
|
||||
|
@ -33,11 +39,24 @@ export default class Welcome extends React.PureComponent {
|
|||
pageUrl = 'welcome.html';
|
||||
}
|
||||
|
||||
const {hsUrl, isUrl} = this.props.serverConfig;
|
||||
const tmpClient = Matrix.createClient({
|
||||
baseUrl: hsUrl,
|
||||
idBaseUrl: isUrl,
|
||||
});
|
||||
const plaf = PlatformPeg.get();
|
||||
const callbackUrl = plaf.getSSOCallbackUrl(tmpClient.getHomeserverUrl(), tmpClient.getIdentityServerUrl());
|
||||
|
||||
return (
|
||||
<AuthPage>
|
||||
<div className="mx_Welcome">
|
||||
<EmbeddedPage className="mx_WelcomePage"
|
||||
<EmbeddedPage
|
||||
className="mx_WelcomePage"
|
||||
url={pageUrl}
|
||||
replaceMap={{
|
||||
"$riot:ssoUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "sso"),
|
||||
"$riot:casUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "cas"),
|
||||
}}
|
||||
/>
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
|
|
|
@ -74,7 +74,8 @@ export default createReactClass({
|
|||
this.context.removeListener('sync', this.onClientSync);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps) {
|
||||
// work out if we need to call setState (if the image URLs array has changed)
|
||||
const newState = this._getState(nextProps);
|
||||
const newImageUrls = newState.imageUrls;
|
||||
|
|
|
@ -51,7 +51,8 @@ export default createReactClass({
|
|||
return this._getState(this.props);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps) {
|
||||
this.setState(this._getState(nextProps));
|
||||
},
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
this._button = createRef();
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) {
|
||||
throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user");
|
||||
}
|
||||
|
|
|
@ -63,7 +63,8 @@ export default createReactClass({
|
|||
}
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps: function(newProps) {
|
||||
this.setState({
|
||||
urls: this.getImageUrls(newProps),
|
||||
});
|
||||
|
|
|
@ -61,7 +61,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
|
||||
this._checkPermissions();
|
||||
},
|
||||
|
|
|
@ -82,7 +82,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this._unmounted = false;
|
||||
},
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export default class StatusMessageContextMenu extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
const { user } = this.props;
|
||||
if (!user) {
|
||||
return;
|
||||
|
|
|
@ -107,6 +107,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._textinput = createRef();
|
||||
},
|
||||
|
|
|
@ -86,7 +86,8 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount() {
|
||||
// TODO: [REACT-WARNING] Move this to constructor
|
||||
UNSAFE_componentWillMount() {
|
||||
this._matrixClient = MatrixClientPeg.get();
|
||||
},
|
||||
|
||||
|
|
|
@ -166,7 +166,6 @@ export default class BugReportDialog extends React.Component {
|
|||
) }
|
||||
</b></p>
|
||||
<Field
|
||||
id="mx_BugReportDialog_issueUrl"
|
||||
type="text"
|
||||
className="mx_BugReportDialog_field_input"
|
||||
label={_t("GitHub issue")}
|
||||
|
@ -175,7 +174,6 @@ export default class BugReportDialog extends React.Component {
|
|||
placeholder="https://github.com/vector-im/riot-web/issues/..."
|
||||
/>
|
||||
<Field
|
||||
id="mx_BugReportDialog_notes"
|
||||
className="mx_BugReportDialog_field_input"
|
||||
element="textarea"
|
||||
label={_t("Notes")}
|
||||
|
|
|
@ -55,7 +55,8 @@ export default createReactClass({
|
|||
askReason: false,
|
||||
}),
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO: [REACT-WARNING] Move this to constructor
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._reasonField = null;
|
||||
},
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ export default createReactClass({
|
|||
const domain = MatrixClientPeg.get().getDomain();
|
||||
aliasField = (
|
||||
<div className="mx_CreateRoomDialog_aliasContainer">
|
||||
<RoomAliasField id="alias" ref={ref => this._aliasFieldRef = ref} onChange={this.onAliasChange} domain={domain} value={this.state.alias} />
|
||||
<RoomAliasField ref={ref => this._aliasFieldRef = ref} onChange={this.onAliasChange} domain={domain} value={this.state.alias} />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
@ -188,8 +188,8 @@ export default createReactClass({
|
|||
>
|
||||
<form onSubmit={this.onOk} onKeyDown={this._onKeyDown}>
|
||||
<div className="mx_Dialog_content">
|
||||
<Field id="name" ref={ref => this._nameFieldRef = ref} label={ _t('Name') } onChange={this.onNameChange} onValidate={this.onNameValidate} value={this.state.name} className="mx_CreateRoomDialog_name" />
|
||||
<Field id="topic" label={ _t('Topic (optional)') } onChange={this.onTopicChange} value={this.state.topic} />
|
||||
<Field ref={ref => this._nameFieldRef = ref} label={ _t('Name') } onChange={this.onNameChange} onValidate={this.onNameValidate} value={this.state.name} className="mx_CreateRoomDialog_name" />
|
||||
<Field label={ _t('Topic (optional)') } onChange={this.onTopicChange} value={this.state.topic} />
|
||||
<LabelledToggleSwitch label={ _t("Make this room public")} onChange={this.onPublicChange} value={this.state.isPublic} />
|
||||
{ privateLabel }
|
||||
{ publicLabel }
|
||||
|
|
|
@ -174,7 +174,6 @@ export default class DeactivateAccountDialog extends React.Component {
|
|||
|
||||
<p>{ _t("To continue, please enter your password:") }</p>
|
||||
<Field
|
||||
id="mx_DeactivateAccountDialog_password"
|
||||
type="password"
|
||||
label={_t('Password')}
|
||||
onChange={this._onPasswordFieldChange}
|
||||
|
|
|
@ -279,6 +279,7 @@ export default class DeviceVerifyDialog extends React.Component {
|
|||
onDone={this._onSasMatchesClick}
|
||||
isSelf={MatrixClientPeg.get().getUserId() === this.props.userId}
|
||||
onStartEmoji={this._onUseSasClick}
|
||||
inDialog={true}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
|
@ -267,7 +267,8 @@ class FilteredList extends React.PureComponent {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase
|
||||
if (this.props.children === nextProps.children && this.props.query === nextProps.query) return;
|
||||
this.setState({
|
||||
filteredChildren: FilteredList.filterChildren(nextProps.children, nextProps.query),
|
||||
|
@ -302,7 +303,7 @@ class FilteredList extends React.PureComponent {
|
|||
render() {
|
||||
const TruncatedList = sdk.getComponent("elements.TruncatedList");
|
||||
return <div>
|
||||
<Field id="DevtoolsDialog_FilteredList_filter" label={_t('Filter results')} autoFocus={true} size={64}
|
||||
<Field label={_t('Filter results')} autoFocus={true} size={64}
|
||||
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery}
|
||||
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
|
||||
// force re-render so that autoFocus is applied when this component is re-used
|
||||
|
|
|
@ -196,7 +196,8 @@ export default class IncomingSasDialog extends React.Component {
|
|||
sas={this._showSasEvent.sas}
|
||||
onCancel={this._onCancelClick}
|
||||
onDone={this._onSasMatchesClick}
|
||||
isSelf={this.props.verifier.userId == MatrixClientPeg.get().getUserId()}
|
||||
isSelf={this.props.verifier.userId === MatrixClientPeg.get().getUserId()}
|
||||
inDialog={true}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2020 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.
|
||||
|
@ -23,6 +24,7 @@ import * as sdk from '../../../index';
|
|||
import { _t } from '../../../languageHandler';
|
||||
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth";
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'InteractiveAuthDialog',
|
||||
|
@ -44,12 +46,36 @@ export default createReactClass({
|
|||
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
|
||||
// Optional title and body to show when not showing a particular stage
|
||||
title: PropTypes.string,
|
||||
body: PropTypes.string,
|
||||
|
||||
// Optional title and body pairs for particular stages and phases within
|
||||
// those stages. Object structure/example is:
|
||||
// {
|
||||
// "org.example.stage_type": {
|
||||
// 1: {
|
||||
// "body": "This is a body for phase 1" of org.example.stage_type,
|
||||
// "title": "Title for phase 1 of org.example.stage_type"
|
||||
// },
|
||||
// 2: {
|
||||
// "body": "This is a body for phase 2 of org.example.stage_type",
|
||||
// "title": "Title for phase 2 of org.example.stage_type"
|
||||
// "continueText": "Confirm identity with Example Auth",
|
||||
// "continueKind": "danger"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
aestheticsForStagePhases: PropTypes.object,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
authError: null,
|
||||
|
||||
// See _onUpdateStagePhase()
|
||||
uiaStage: null,
|
||||
uiaStagePhase: null,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -57,12 +83,21 @@ export default createReactClass({
|
|||
if (success) {
|
||||
this.props.onFinished(true, result);
|
||||
} else {
|
||||
this.setState({
|
||||
authError: result,
|
||||
});
|
||||
if (result === ERROR_USER_CANCELLED) {
|
||||
this.props.onFinished(false, null);
|
||||
} else {
|
||||
this.setState({
|
||||
authError: result,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onUpdateStagePhase: function(newStage, newPhase) {
|
||||
// We copy the stage and stage phase params into state for title selection in render()
|
||||
this.setState({uiaStage: newStage, uiaStagePhase: newPhase});
|
||||
},
|
||||
|
||||
_onDismissClick: function() {
|
||||
this.props.onFinished(false);
|
||||
},
|
||||
|
@ -71,6 +106,23 @@ export default createReactClass({
|
|||
const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth");
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
|
||||
// Let's pick a title, body, and other params text that we'll show to the user. The order
|
||||
// is most specific first, so stagePhase > our props > defaults.
|
||||
|
||||
let title = this.state.authError ? 'Error' : (this.props.title || _t('Authentication'));
|
||||
let body = this.state.authError ? null : this.props.body;
|
||||
let continueText = null;
|
||||
let continueKind = null;
|
||||
if (!this.state.authError && this.props.aestheticsForStagePhases) {
|
||||
if (this.props.aestheticsForStagePhases[this.state.uiaStage]) {
|
||||
const aesthetics = this.props.aestheticsForStagePhases[this.state.uiaStage][this.state.uiaStagePhase];
|
||||
if (aesthetics && aesthetics.title) title = aesthetics.title;
|
||||
if (aesthetics && aesthetics.body) body = aesthetics.body;
|
||||
if (aesthetics && aesthetics.continueText) continueText = aesthetics.continueText;
|
||||
if (aesthetics && aesthetics.continueKind) continueKind = aesthetics.continueKind;
|
||||
}
|
||||
}
|
||||
|
||||
let content;
|
||||
if (this.state.authError) {
|
||||
content = (
|
||||
|
@ -88,11 +140,16 @@ export default createReactClass({
|
|||
} else {
|
||||
content = (
|
||||
<div id='mx_Dialog_content'>
|
||||
<InteractiveAuth ref={this._collectInteractiveAuth}
|
||||
{body}
|
||||
<InteractiveAuth
|
||||
ref={this._collectInteractiveAuth}
|
||||
matrixClient={this.props.matrixClient}
|
||||
authData={this.props.authData}
|
||||
makeRequest={this.props.makeRequest}
|
||||
onAuthFinished={this._onAuthFinished}
|
||||
onStagePhaseChange={this._onUpdateStagePhase}
|
||||
continueText={continueText}
|
||||
continueKind={continueKind}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -101,7 +158,7 @@ export default createReactClass({
|
|||
return (
|
||||
<BaseDialog className="mx_InteractiveAuthDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={this.state.authError ? 'Error' : (this.props.title || _t('Authentication'))}
|
||||
title={title}
|
||||
contentId='mx_Dialog_content'
|
||||
>
|
||||
{ content }
|
||||
|
|
|
@ -123,7 +123,6 @@ export default class ReportEventDialog extends PureComponent {
|
|||
</p>
|
||||
{adminMessage}
|
||||
<Field
|
||||
id="mx_ReportEventDialog_reason"
|
||||
className="mx_ReportEventDialog_reason"
|
||||
element="textarea"
|
||||
label={_t("Reason")}
|
||||
|
|
|
@ -36,12 +36,12 @@ export default class RoomSettingsDialog extends React.Component {
|
|||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
this._dispatcherRef = dis.register(this._onAction);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
dis.unregister(this._dispatcherRef);
|
||||
if (this._dispatcherRef) dis.unregister(this._dispatcherRef);
|
||||
}
|
||||
|
||||
_onAction = (payload) => {
|
||||
|
@ -72,7 +72,7 @@ export default class RoomSettingsDialog extends React.Component {
|
|||
));
|
||||
tabs.push(new Tab(
|
||||
_td("Notifications"),
|
||||
"mx_RoomSettingsDialog_rolesIcon",
|
||||
"mx_RoomSettingsDialog_notificationsIcon",
|
||||
<NotificationSettingsTab roomId={this.props.roomId} />,
|
||||
));
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ export default createReactClass({
|
|||
onFinished: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentWillMount: async function() {
|
||||
componentDidMount: async function() {
|
||||
const recommended = await this.props.room.getRecommendedVersion();
|
||||
this._targetVersion = recommended.version;
|
||||
this.setState({busy: false});
|
||||
|
|
|
@ -62,6 +62,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._input_value = createRef();
|
||||
this._uiAuth = createRef();
|
||||
|
|
|
@ -75,8 +75,8 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
console.info('SetPasswordDialog component will mount');
|
||||
componentDidMount: function() {
|
||||
console.info('SetPasswordDialog component did mount');
|
||||
},
|
||||
|
||||
_onPasswordChanged: function(res) {
|
||||
|
|
|
@ -121,7 +121,7 @@ export default class ShareDialog extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
if (this.props.target instanceof Room) {
|
||||
const permalinkCreator = new RoomPermalinkCreator(this.props.target);
|
||||
permalinkCreator.load();
|
||||
|
|
|
@ -16,14 +16,14 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import {CommandCategories, CommandMap} from "../../../SlashCommands";
|
||||
import {CommandCategories, Commands} from "../../../SlashCommands";
|
||||
import * as sdk from "../../../index";
|
||||
|
||||
export default ({onFinished}) => {
|
||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
||||
|
||||
const categories = {};
|
||||
Object.values(CommandMap).forEach(cmd => {
|
||||
Commands.forEach(cmd => {
|
||||
if (!categories[cmd.category]) {
|
||||
categories[cmd.category] = [];
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ export default ({onFinished}) => {
|
|||
|
||||
categories[category].forEach(cmd => {
|
||||
rows.push(<tr key={cmd.command}>
|
||||
<td><strong>{cmd.command}</strong></td>
|
||||
<td><strong>{cmd.getCommand()}</strong></td>
|
||||
<td>{cmd.args}</td>
|
||||
<td>{cmd.description}</td>
|
||||
</tr>);
|
||||
|
|
|
@ -55,6 +55,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._field = createRef();
|
||||
},
|
||||
|
@ -116,7 +117,6 @@ export default createReactClass({
|
|||
</div>
|
||||
<div>
|
||||
<Field
|
||||
id="mx_TextInputDialog_field"
|
||||
className="mx_TextInputDialog_input"
|
||||
ref={this._field}
|
||||
type="text"
|
||||
|
|
|
@ -87,7 +87,7 @@ export default createReactClass({
|
|||
onSend: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
MatrixClientPeg.get().on("deviceVerificationChanged", this._onDeviceVerificationChanged);
|
||||
},
|
||||
|
||||
|
|
|
@ -30,16 +30,29 @@ export default class VerificationRequestDialog extends React.Component {
|
|||
constructor(...args) {
|
||||
super(...args);
|
||||
this.onFinished = this.onFinished.bind(this);
|
||||
this.state = {};
|
||||
if (this.props.verificationRequest) {
|
||||
this.state.verificationRequest = this.props.verificationRequest;
|
||||
} else if (this.props.verificationRequestPromise) {
|
||||
this.props.verificationRequestPromise.then(r => {
|
||||
this.setState({verificationRequest: r});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
|
||||
const EncryptionPanel = sdk.getComponent("views.right_panel.EncryptionPanel");
|
||||
const request = this.state.verificationRequest;
|
||||
const otherUserId = request && request.otherUserId;
|
||||
const member = this.props.member ||
|
||||
MatrixClientPeg.get().getUser(this.props.verificationRequest.otherUserId);
|
||||
otherUserId && MatrixClientPeg.get().getUser(otherUserId);
|
||||
const title = request && request.isSelfVerification ?
|
||||
_t("Verify this session") : _t("Verification Request");
|
||||
|
||||
return <BaseDialog className="mx_InfoDialog" onFinished={this.onFinished}
|
||||
contentId="mx_Dialog_content"
|
||||
title={_t("Verification Request")}
|
||||
title={title}
|
||||
hasCancel={true}
|
||||
>
|
||||
<EncryptionPanel
|
||||
|
@ -48,6 +61,7 @@ export default class VerificationRequestDialog extends React.Component {
|
|||
verificationRequestPromise={this.props.verificationRequestPromise}
|
||||
onClose={this.props.onFinished}
|
||||
member={member}
|
||||
inDialog={true}
|
||||
/>
|
||||
</BaseDialog>;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,8 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(props) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps: function(props) {
|
||||
// Make sure the selected item isn't outside the list bounds
|
||||
const selected = this.state.selected;
|
||||
const maxSelected = this._maxSelected(props.addressList);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2020 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.
|
||||
|
@ -41,12 +42,30 @@ import PersistedElement from "./PersistedElement";
|
|||
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
||||
const ENABLE_REACT_PERF = false;
|
||||
|
||||
/**
|
||||
* Does template substitution on a URL (or any string). Variables will be
|
||||
* passed through encodeURIComponent.
|
||||
* @param {string} uriTemplate The path with template variables e.g. '/foo/$bar'.
|
||||
* @param {Object} variables The key/value pairs to replace the template
|
||||
* variables with. E.g. { '$bar': 'baz' }.
|
||||
* @return {string} The result of replacing all template variables e.g. '/foo/baz'.
|
||||
*/
|
||||
function uriFromTemplate(uriTemplate, variables) {
|
||||
let out = uriTemplate;
|
||||
for (const [key, val] of Object.entries(variables)) {
|
||||
out = out.replace(
|
||||
'$' + key, encodeURIComponent(val),
|
||||
);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export default class AppTile extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// The key used for PersistedElement
|
||||
this._persistKey = 'widget_' + this.props.id;
|
||||
this._persistKey = 'widget_' + this.props.app.id;
|
||||
|
||||
this.state = this._getNewState(props);
|
||||
|
||||
|
@ -78,7 +97,7 @@ export default class AppTile extends React.Component {
|
|||
// This is a function to make the impact of calling SettingsStore slightly less
|
||||
const hasPermissionToLoad = () => {
|
||||
const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", newProps.room.roomId);
|
||||
return !!currentlyAllowedWidgets[newProps.eventId];
|
||||
return !!currentlyAllowedWidgets[newProps.app.eventId];
|
||||
};
|
||||
|
||||
const PersistedElement = sdk.getComponent("elements.PersistedElement");
|
||||
|
@ -86,7 +105,7 @@ export default class AppTile extends React.Component {
|
|||
initialising: true, // True while we are mangling the widget URL
|
||||
// True while the iframe content is loading
|
||||
loading: this.props.waitForIframeLoad && !PersistedElement.isMounted(this._persistKey),
|
||||
widgetUrl: this._addWurlParams(newProps.url),
|
||||
widgetUrl: this._addWurlParams(newProps.app.url),
|
||||
// Assume that widget has permission to load if we are the user who
|
||||
// added it to the room, or if explicitly granted by the user
|
||||
hasPermissionToLoad: newProps.userId === newProps.creatorUserId || hasPermissionToLoad(),
|
||||
|
@ -103,7 +122,7 @@ export default class AppTile extends React.Component {
|
|||
* @return {Boolean} True if capability supported
|
||||
*/
|
||||
_hasCapability(capability) {
|
||||
return ActiveWidgetStore.widgetHasCapability(this.props.id, capability);
|
||||
return ActiveWidgetStore.widgetHasCapability(this.props.app.id, capability);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,7 +144,7 @@ export default class AppTile extends React.Component {
|
|||
|
||||
const params = qs.parse(u.query);
|
||||
// Append widget ID to query parameters
|
||||
params.widgetId = this.props.id;
|
||||
params.widgetId = this.props.app.id;
|
||||
// Append current / parent URL, minus the hash because that will change when
|
||||
// we view a different room (ie. may change for persistent widgets)
|
||||
params.parentUrl = window.location.href.split('#', 2)[0];
|
||||
|
@ -137,35 +156,33 @@ export default class AppTile extends React.Component {
|
|||
|
||||
isMixedContent() {
|
||||
const parentContentProtocol = window.location.protocol;
|
||||
const u = url.parse(this.props.url);
|
||||
const u = url.parse(this.props.app.url);
|
||||
const childContentProtocol = u.protocol;
|
||||
if (parentContentProtocol === 'https:' && childContentProtocol !== 'https:') {
|
||||
console.warn("Refusing to load mixed-content app:",
|
||||
parentContentProtocol, childContentProtocol, window.location, this.props.url);
|
||||
parentContentProtocol, childContentProtocol, window.location, this.props.app.url);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
// Only fetch IM token on mount if we're showing and have permission to load
|
||||
if (this.props.show && this.state.hasPermissionToLoad) {
|
||||
this.setScalarToken();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Widget action listeners
|
||||
this.dispatcherRef = dis.register(this._onAction);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Widget action listeners
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
|
||||
// if it's not remaining on screen, get rid of the PersistedElement container
|
||||
if (!ActiveWidgetStore.getWidgetPersistence(this.props.id)) {
|
||||
ActiveWidgetStore.destroyPersistentWidget(this.props.id);
|
||||
if (!ActiveWidgetStore.getWidgetPersistence(this.props.app.id)) {
|
||||
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
|
||||
const PersistedElement = sdk.getComponent("elements.PersistedElement");
|
||||
PersistedElement.destroyElement(this._persistKey);
|
||||
}
|
||||
|
@ -176,11 +193,11 @@ export default class AppTile extends React.Component {
|
|||
* Component initialisation is only complete when this function has resolved
|
||||
*/
|
||||
setScalarToken() {
|
||||
if (!WidgetUtils.isScalarUrl(this.props.url)) {
|
||||
if (!WidgetUtils.isScalarUrl(this.props.app.url)) {
|
||||
console.warn('Non-scalar widget, not setting scalar token!', url);
|
||||
this.setState({
|
||||
error: null,
|
||||
widgetUrl: this._addWurlParams(this.props.url),
|
||||
widgetUrl: this._addWurlParams(this.props.app.url),
|
||||
initialising: false,
|
||||
});
|
||||
return;
|
||||
|
@ -191,7 +208,7 @@ export default class AppTile extends React.Component {
|
|||
console.warn("No integration manager - not setting scalar token", url);
|
||||
this.setState({
|
||||
error: null,
|
||||
widgetUrl: this._addWurlParams(this.props.url),
|
||||
widgetUrl: this._addWurlParams(this.props.app.url),
|
||||
initialising: false,
|
||||
});
|
||||
return;
|
||||
|
@ -204,7 +221,7 @@ export default class AppTile extends React.Component {
|
|||
console.warn('Non-scalar manager, not setting scalar token!', url);
|
||||
this.setState({
|
||||
error: null,
|
||||
widgetUrl: this._addWurlParams(this.props.url),
|
||||
widgetUrl: this._addWurlParams(this.props.app.url),
|
||||
initialising: false,
|
||||
});
|
||||
return;
|
||||
|
@ -217,7 +234,7 @@ export default class AppTile extends React.Component {
|
|||
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));
|
||||
const u = url.parse(this._addWurlParams(this.props.app.url));
|
||||
const params = qs.parse(u.query);
|
||||
if (!params.scalar_token) {
|
||||
params.scalar_token = encodeURIComponent(token);
|
||||
|
@ -245,8 +262,9 @@ export default class AppTile extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.url !== this.props.url) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase
|
||||
if (nextProps.app.url !== this.props.app.url) {
|
||||
this._getNewState(nextProps);
|
||||
// Fetch IM token for new URL if we're showing and have permission to load
|
||||
if (this.props.show && this.state.hasPermissionToLoad) {
|
||||
|
@ -280,7 +298,7 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
|
||||
_onEditClick() {
|
||||
console.log("Edit widget ID ", this.props.id);
|
||||
console.log("Edit widget ID ", this.props.app.id);
|
||||
if (this.props.onEditClick) {
|
||||
this.props.onEditClick();
|
||||
} else {
|
||||
|
@ -289,21 +307,21 @@ export default class AppTile extends React.Component {
|
|||
IntegrationManagers.sharedInstance().openAll(
|
||||
this.props.room,
|
||||
'type_' + this.props.type,
|
||||
this.props.id,
|
||||
this.props.app.id,
|
||||
);
|
||||
} else {
|
||||
IntegrationManagers.sharedInstance().getPrimaryManager().open(
|
||||
this.props.room,
|
||||
'type_' + this.props.type,
|
||||
this.props.id,
|
||||
this.props.app.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onSnapshotClick() {
|
||||
console.warn("Requesting widget snapshot");
|
||||
ActiveWidgetStore.getWidgetMessaging(this.props.id).getScreenshot()
|
||||
console.log("Requesting widget snapshot");
|
||||
ActiveWidgetStore.getWidgetMessaging(this.props.app.id).getScreenshot()
|
||||
.catch((err) => {
|
||||
console.error("Failed to get screenshot", err);
|
||||
})
|
||||
|
@ -351,7 +369,7 @@ export default class AppTile extends React.Component {
|
|||
|
||||
WidgetUtils.setRoomWidget(
|
||||
this.props.room.roomId,
|
||||
this.props.id,
|
||||
this.props.app.id,
|
||||
).catch((e) => {
|
||||
console.error('Failed to delete widget', e);
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
@ -369,7 +387,7 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
|
||||
_onRevokeClicked() {
|
||||
console.info("Revoke widget permissions - %s", this.props.id);
|
||||
console.info("Revoke widget permissions - %s", this.props.app.id);
|
||||
this._revokeWidgetPermission();
|
||||
}
|
||||
|
||||
|
@ -380,10 +398,10 @@ export default class AppTile extends React.Component {
|
|||
// Destroy the old widget messaging before starting it back up again. Some widgets
|
||||
// have startup routines that run when they are loaded, so we just need to reinitialize
|
||||
// the messaging for them.
|
||||
ActiveWidgetStore.delWidgetMessaging(this.props.id);
|
||||
ActiveWidgetStore.delWidgetMessaging(this.props.app.id);
|
||||
this._setupWidgetMessaging();
|
||||
|
||||
ActiveWidgetStore.setRoomId(this.props.id, this.props.room.roomId);
|
||||
ActiveWidgetStore.setRoomId(this.props.app.id, this.props.room.roomId);
|
||||
this.setState({loading: false});
|
||||
}
|
||||
|
||||
|
@ -391,10 +409,10 @@ export default class AppTile extends React.Component {
|
|||
// FIXME: There's probably no reason to do this here: it should probably be done entirely
|
||||
// in ActiveWidgetStore.
|
||||
const widgetMessaging = new WidgetMessaging(
|
||||
this.props.id, this.props.url, this.props.userWidget, this._appFrame.current.contentWindow);
|
||||
ActiveWidgetStore.setWidgetMessaging(this.props.id, widgetMessaging);
|
||||
this.props.app.id, this._getRenderedUrl(), this.props.userWidget, this._appFrame.current.contentWindow);
|
||||
ActiveWidgetStore.setWidgetMessaging(this.props.app.id, widgetMessaging);
|
||||
widgetMessaging.getCapabilities().then((requestedCapabilities) => {
|
||||
console.log(`Widget ${this.props.id} requested capabilities: ` + requestedCapabilities);
|
||||
console.log(`Widget ${this.props.app.id} requested capabilities: ` + requestedCapabilities);
|
||||
requestedCapabilities = requestedCapabilities || [];
|
||||
|
||||
// Allow whitelisted capabilities
|
||||
|
@ -406,7 +424,7 @@ export default class AppTile extends React.Component {
|
|||
}, this.props.whitelistCapabilities);
|
||||
|
||||
if (requestedWhitelistCapabilies.length > 0 ) {
|
||||
console.warn(`Widget ${this.props.id} allowing requested, whitelisted properties: ` +
|
||||
console.log(`Widget ${this.props.app.id} allowing requested, whitelisted properties: ` +
|
||||
requestedWhitelistCapabilies,
|
||||
);
|
||||
}
|
||||
|
@ -414,7 +432,7 @@ export default class AppTile extends React.Component {
|
|||
|
||||
// TODO -- Add UI to warn about and optionally allow requested capabilities
|
||||
|
||||
ActiveWidgetStore.setWidgetCapabilities(this.props.id, requestedWhitelistCapabilies);
|
||||
ActiveWidgetStore.setWidgetCapabilities(this.props.app.id, requestedWhitelistCapabilies);
|
||||
|
||||
if (this.props.onCapabilityRequest) {
|
||||
this.props.onCapabilityRequest(requestedCapabilities);
|
||||
|
@ -422,16 +440,16 @@ export default class AppTile extends React.Component {
|
|||
|
||||
// We only tell Jitsi widgets that we're ready because they're realistically the only ones
|
||||
// using this custom extension to the widget API.
|
||||
if (this.props.type === 'jitsi') {
|
||||
if (this.props.app.type === 'jitsi') {
|
||||
widgetMessaging.flagReadyToContinue();
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(`Failed to get capabilities for widget type ${this.props.type}`, this.props.id, err);
|
||||
console.log(`Failed to get capabilities for widget type ${this.props.app.type}`, this.props.app.id, err);
|
||||
});
|
||||
}
|
||||
|
||||
_onAction(payload) {
|
||||
if (payload.widgetId === this.props.id) {
|
||||
if (payload.widgetId === this.props.app.id) {
|
||||
switch (payload.action) {
|
||||
case 'm.sticker':
|
||||
if (this._hasCapability('m.sticker')) {
|
||||
|
@ -460,9 +478,9 @@ export default class AppTile extends React.Component {
|
|||
|
||||
_grantWidgetPermission() {
|
||||
const roomId = this.props.room.roomId;
|
||||
console.info("Granting permission for widget to load: " + this.props.eventId);
|
||||
console.info("Granting permission for widget to load: " + this.props.app.eventId);
|
||||
const current = SettingsStore.getValue("allowedWidgets", roomId);
|
||||
current[this.props.eventId] = true;
|
||||
current[this.props.app.eventId] = true;
|
||||
SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).then(() => {
|
||||
this.setState({hasPermissionToLoad: true});
|
||||
|
||||
|
@ -476,14 +494,14 @@ export default class AppTile extends React.Component {
|
|||
|
||||
_revokeWidgetPermission() {
|
||||
const roomId = this.props.room.roomId;
|
||||
console.info("Revoking permission for widget to load: " + this.props.eventId);
|
||||
console.info("Revoking permission for widget to load: " + this.props.app.eventId);
|
||||
const current = SettingsStore.getValue("allowedWidgets", roomId);
|
||||
current[this.props.eventId] = false;
|
||||
current[this.props.app.eventId] = false;
|
||||
SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).then(() => {
|
||||
this.setState({hasPermissionToLoad: false});
|
||||
|
||||
// Force the widget to be non-persistent (able to be deleted/forgotten)
|
||||
ActiveWidgetStore.destroyPersistentWidget(this.props.id);
|
||||
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
|
||||
const PersistedElement = sdk.getComponent("elements.PersistedElement");
|
||||
PersistedElement.destroyElement(this._persistKey);
|
||||
}).catch(err => {
|
||||
|
@ -494,8 +512,8 @@ export default class AppTile extends React.Component {
|
|||
|
||||
formatAppTileName() {
|
||||
let appTileName = "No name";
|
||||
if (this.props.name && this.props.name.trim()) {
|
||||
appTileName = this.props.name.trim();
|
||||
if (this.props.app.name && this.props.app.name.trim()) {
|
||||
appTileName = this.props.app.name.trim();
|
||||
}
|
||||
return appTileName;
|
||||
}
|
||||
|
@ -519,20 +537,78 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
_getSafeUrl() {
|
||||
const parsedWidgetUrl = url.parse(this.state.widgetUrl, true);
|
||||
/**
|
||||
* Replace the widget template variables in a url with their values
|
||||
*
|
||||
* @param {string} u The URL with template variables
|
||||
*
|
||||
* @returns {string} url with temlate variables replaced
|
||||
*/
|
||||
_templatedUrl(u) {
|
||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
const myUser = MatrixClientPeg.get().getUser(myUserId);
|
||||
const vars = Object.assign({
|
||||
domain: "jitsi.riot.im", // v1 widgets have this hardcoded
|
||||
}, this.props.app.data, {
|
||||
'matrix_user_id': myUserId,
|
||||
'matrix_room_id': this.props.room.roomId,
|
||||
'matrix_display_name': myUser ? myUser.displayName : myUserId,
|
||||
'matrix_avatar_url': myUser ? MatrixClientPeg.get().mxcUrlToHttp(myUser.avatarUrl) : '',
|
||||
|
||||
// TODO: Namespace themes through some standard
|
||||
'theme': SettingsStore.getValue("theme"),
|
||||
});
|
||||
|
||||
if (vars.conferenceId === undefined) {
|
||||
// we'll need to parse the conference ID out of the URL for v1 Jitsi widgets
|
||||
const parsedUrl = new URL(this.props.app.url);
|
||||
vars.conferenceId = parsedUrl.searchParams.get("confId");
|
||||
}
|
||||
|
||||
return uriFromTemplate(u, vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL used in the iframe
|
||||
* In cases where we supply our own UI for a widget, this is an internal
|
||||
* URL different to the one used if the widget is popped out to a separate
|
||||
* tab / browser
|
||||
*
|
||||
* @returns {string} url
|
||||
*/
|
||||
_getRenderedUrl() {
|
||||
let url;
|
||||
|
||||
if (this.props.app.type === 'jitsi') {
|
||||
console.log("Replacing Jitsi widget URL with local wrapper");
|
||||
url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true});
|
||||
url = this._addWurlParams(url);
|
||||
} else {
|
||||
url = this._getSafeUrl(this.state.widgetUrl);
|
||||
}
|
||||
return this._templatedUrl(url);
|
||||
}
|
||||
|
||||
_getPopoutUrl() {
|
||||
if (this.props.app.type === 'jitsi') {
|
||||
return this._templatedUrl(
|
||||
WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}),
|
||||
);
|
||||
} else {
|
||||
// use app.url, not state.widgetUrl, because we want the one without
|
||||
// the wURL params for the popped-out version.
|
||||
return this._templatedUrl(this._getSafeUrl(this.props.app.url));
|
||||
}
|
||||
}
|
||||
|
||||
_getSafeUrl(u) {
|
||||
const parsedWidgetUrl = url.parse(u, true);
|
||||
if (ENABLE_REACT_PERF) {
|
||||
parsedWidgetUrl.search = null;
|
||||
parsedWidgetUrl.query.react_perf = true;
|
||||
}
|
||||
let safeWidgetUrl = '';
|
||||
if (ALLOWED_APP_URL_SCHEMES.includes(parsedWidgetUrl.protocol) || (
|
||||
// Check if the widget URL is a Jitsi widget in Electron
|
||||
parsedWidgetUrl.protocol === 'vector:'
|
||||
&& parsedWidgetUrl.host === 'vector'
|
||||
&& parsedWidgetUrl.pathname === '/webapp/jitsi.html'
|
||||
&& this.props.type === 'jitsi'
|
||||
)) {
|
||||
if (ALLOWED_APP_URL_SCHEMES.includes(parsedWidgetUrl.protocol)) {
|
||||
safeWidgetUrl = url.format(parsedWidgetUrl);
|
||||
}
|
||||
return safeWidgetUrl;
|
||||
|
@ -562,9 +638,9 @@ export default class AppTile extends React.Component {
|
|||
|
||||
_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');
|
||||
// window.open(this._getPopoutUrl(), '_blank', 'noopener=yes');
|
||||
Object.assign(document.createElement('a'),
|
||||
{ target: '_blank', href: this._getSafeUrl(), rel: 'noreferrer noopener'}).click();
|
||||
{ target: '_blank', href: this._getPopoutUrl(), rel: 'noreferrer noopener'}).click();
|
||||
}
|
||||
|
||||
_onReloadWidgetClick() {
|
||||
|
@ -641,7 +717,7 @@ export default class AppTile extends React.Component {
|
|||
<iframe
|
||||
allow={iframeFeatures}
|
||||
ref={this._appFrame}
|
||||
src={this._getSafeUrl()}
|
||||
src={this._getRenderedUrl()}
|
||||
allowFullScreen={true}
|
||||
sandbox={sandboxFlags}
|
||||
onLoad={this._onLoaded} />
|
||||
|
@ -706,7 +782,7 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<div className={appTileClass} id={this.props.id}>
|
||||
<div className={appTileClass} id={this.props.app.id}>
|
||||
{ this.props.showMenubar &&
|
||||
<div ref={this._menu_bar} className={menuBarClasses} onClick={this.onClickMenuBar}>
|
||||
<span className="mx_AppTileMenuBarTitle" style={{pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false)}}>
|
||||
|
@ -753,12 +829,8 @@ export default class AppTile extends React.Component {
|
|||
AppTile.displayName = 'AppTile';
|
||||
|
||||
AppTile.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
eventId: PropTypes.string, // required for room widgets
|
||||
url: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
app: PropTypes.object.isRequired,
|
||||
room: PropTypes.object.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
// Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer.
|
||||
// This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
|
||||
fullWidth: PropTypes.bool,
|
||||
|
@ -805,7 +877,6 @@ AppTile.propTypes = {
|
|||
};
|
||||
|
||||
AppTile.defaultProps = {
|
||||
url: "",
|
||||
waitForIframeLoad: true,
|
||||
showMenubar: true,
|
||||
showTitle: true,
|
||||
|
|
|
@ -38,7 +38,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
},
|
||||
|
|
|
@ -116,7 +116,8 @@ export default class Dropdown extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount() { // eslint-disable-line camelcase
|
||||
this._button = createRef();
|
||||
// Listen for all clicks on the document so we can close the
|
||||
// menu when the user clicks somewhere else
|
||||
|
@ -127,7 +128,8 @@ export default class Dropdown extends React.Component {
|
|||
document.removeEventListener('click', this._onDocumentClick, false);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase
|
||||
if (!nextProps.children || nextProps.children.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ export default class EditableItemList extends React.Component {
|
|||
return (
|
||||
<form onSubmit={this._onItemAdded} autoComplete="off"
|
||||
noValidate={true} className="mx_EditableItemList_newItem">
|
||||
<Field id={`mx_EditableItemList_new_${this.props.id}`} label={this.props.placeholder} type="text"
|
||||
<Field label={this.props.placeholder} type="text"
|
||||
autoComplete="off" value={this.props.newItem || ""} onChange={this._onNewItemChanged}
|
||||
list={this.props.suggestionsListId} />
|
||||
<AccessibleButton onClick={this._onItemAdded} kind="primary" type="submit">
|
||||
|
|
|
@ -62,7 +62,8 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps) {
|
||||
if (nextProps.initialValue !== this.props.initialValue) {
|
||||
this.value = nextProps.initialValue;
|
||||
if (this._editable_div.current) {
|
||||
|
@ -71,7 +72,8 @@ export default createReactClass({
|
|||
}
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
// we track value as an JS object field rather than in React state
|
||||
// as React doesn't play nice with contentEditable.
|
||||
this.value = '';
|
||||
|
|
|
@ -42,7 +42,7 @@ export default class EditableTextContainer extends React.Component {
|
|||
this._onValueChanged = this._onValueChanged.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
if (this.props.getInitialValue === undefined) {
|
||||
// use whatever was given in the initialValue property.
|
||||
return;
|
||||
|
|
|
@ -20,6 +20,7 @@ import MemberAvatar from '../avatars/MemberAvatar';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import {MatrixEvent, RoomMember} from "matrix-js-sdk";
|
||||
import {useStateToggle} from "../../../hooks/useStateToggle";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
|
||||
const EventListSummary = ({events, children, threshold=3, onToggle, startExpanded, summaryMembers=[], summaryText}) => {
|
||||
const [expanded, toggleExpanded] = useStateToggle(startExpanded);
|
||||
|
@ -42,24 +43,15 @@ const EventListSummary = ({events, children, threshold=3, onToggle, startExpande
|
|||
);
|
||||
}
|
||||
|
||||
let body;
|
||||
if (expanded) {
|
||||
return (
|
||||
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
||||
<div className={"mx_EventListSummary_toggle"} onClick={toggleExpanded}>
|
||||
{ _t('collapse') }
|
||||
</div>
|
||||
<div className="mx_EventListSummary_line"> </div>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const avatars = summaryMembers.map((m) => <MemberAvatar key={m.userId} member={m} width={14} height={14} />);
|
||||
return (
|
||||
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
||||
<div className={"mx_EventListSummary_toggle"} onClick={toggleExpanded}>
|
||||
{ _t('expand') }
|
||||
</div>
|
||||
body = <React.Fragment>
|
||||
<div className="mx_EventListSummary_line"> </div>
|
||||
{ children }
|
||||
</React.Fragment>;
|
||||
} else {
|
||||
const avatars = summaryMembers.map((m) => <MemberAvatar key={m.userId} member={m} width={14} height={14} />);
|
||||
body = (
|
||||
<div className="mx_EventTile_line">
|
||||
<div className="mx_EventTile_info">
|
||||
<span className="mx_EventListSummary_avatars" onClick={toggleExpanded}>
|
||||
|
@ -70,6 +62,15 @@ const EventListSummary = ({events, children, threshold=3, onToggle, startExpande
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
||||
<AccessibleButton className="mx_EventListSummary_toggle" onClick={toggleExpanded} aria-expanded={expanded}>
|
||||
{ expanded ? _t('collapse') : _t('expand') }
|
||||
</AccessibleButton>
|
||||
{ body }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -23,10 +23,16 @@ import { debounce } from 'lodash';
|
|||
// Invoke validation from user input (when typing, etc.) at most once every N ms.
|
||||
const VALIDATION_THROTTLE_MS = 200;
|
||||
|
||||
const BASE_ID = "mx_Field";
|
||||
let count = 1;
|
||||
function getId() {
|
||||
return `${BASE_ID}_${count++}`;
|
||||
}
|
||||
|
||||
export default class Field extends React.PureComponent {
|
||||
static propTypes = {
|
||||
// The field's ID, which binds the input and label together.
|
||||
id: PropTypes.string.isRequired,
|
||||
// The field's ID, which binds the input and label together. Immutable.
|
||||
id: PropTypes.string,
|
||||
// The element to create. Defaults to "input".
|
||||
// To define options for a select, use <Field><option ... /></Field>
|
||||
element: PropTypes.oneOf(["input", "select", "textarea"]),
|
||||
|
@ -63,13 +69,15 @@ export default class Field extends React.PureComponent {
|
|||
// All other props pass through to the <input>.
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
valid: undefined,
|
||||
feedback: undefined,
|
||||
focused: false,
|
||||
};
|
||||
|
||||
this.id = this.props.id || getId();
|
||||
}
|
||||
|
||||
onFocus = (ev) => {
|
||||
|
@ -167,6 +175,7 @@ export default class Field extends React.PureComponent {
|
|||
inputProps.type = inputProps.type || "text";
|
||||
inputProps.ref = input => this.input = input;
|
||||
inputProps.placeholder = inputProps.placeholder || inputProps.label;
|
||||
inputProps.id = this.id; // this overwrites the id from props
|
||||
|
||||
inputProps.onFocus = this.onFocus;
|
||||
inputProps.onChange = this.onChange;
|
||||
|
@ -211,7 +220,7 @@ export default class Field extends React.PureComponent {
|
|||
return <div className={fieldClasses}>
|
||||
{prefixContainer}
|
||||
{fieldInput}
|
||||
<label htmlFor={this.props.id}>{this.props.label}</label>
|
||||
<label htmlFor={this.id}>{this.props.label}</label>
|
||||
{postfixContainer}
|
||||
{fieldTooltip}
|
||||
</div>;
|
||||
|
|
|
@ -81,7 +81,8 @@ export default class Flair extends React.Component {
|
|||
this._unmounted = true;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase
|
||||
this._generateAvatars(newProps.groups);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,8 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import { _t } from "../../../languageHandler";
|
||||
|
||||
function languageMatchesSearchQuery(query, language) {
|
||||
if (language.label.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
|
||||
if (language.value.toUpperCase() == query.toUpperCase()) return true;
|
||||
if (language.label.toUpperCase().includes(query.toUpperCase())) return true;
|
||||
if (language.value.toUpperCase() === query.toUpperCase()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ export default class LanguageDropdown extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
languageHandler.getAllLanguagesFromJson().then((langs) => {
|
||||
langs.sort(function(a, b) {
|
||||
if (a.label < b.label) return -1;
|
||||
|
|
|
@ -113,10 +113,12 @@ export default class PersistedElement extends React.Component {
|
|||
|
||||
componentDidMount() {
|
||||
this.updateChild();
|
||||
this.renderApp();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.updateChild();
|
||||
this.renderApp();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -141,6 +143,14 @@ export default class PersistedElement extends React.Component {
|
|||
this.updateChildVisibility(this.child, true);
|
||||
}
|
||||
|
||||
renderApp() {
|
||||
const content = <div ref={this.collectChild} style={this.props.style}>
|
||||
{this.props.children}
|
||||
</div>;
|
||||
|
||||
ReactDOM.render(content, getOrCreateContainer('mx_persistedElement_'+this.props.persistKey));
|
||||
}
|
||||
|
||||
updateChildVisibility(child, visible) {
|
||||
if (!child) return;
|
||||
child.style.display = visible ? 'block' : 'none';
|
||||
|
@ -160,12 +170,6 @@ export default class PersistedElement extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const content = <div ref={this.collectChild} style={this.props.style}>
|
||||
{this.props.children}
|
||||
</div>;
|
||||
|
||||
ReactDOM.render(content, getOrCreateContainer('mx_persistedElement_'+this.props.persistKey));
|
||||
|
||||
return <div ref={this.collectChildContainer}></div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019, 2020 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.
|
||||
|
@ -33,7 +33,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
ActiveWidgetStore.on('update', this._onActiveWidgetStoreUpdate);
|
||||
},
|
||||
|
@ -75,11 +75,7 @@ export default createReactClass({
|
|||
const AppTile = sdk.getComponent('elements.AppTile');
|
||||
return <AppTile
|
||||
key={app.id}
|
||||
id={app.id}
|
||||
eventId={app.eventId}
|
||||
url={app.url}
|
||||
name={app.name}
|
||||
type={app.type}
|
||||
app={app}
|
||||
fullWidth={true}
|
||||
room={persistentWidgetInRoom}
|
||||
userId={MatrixClientPeg.get().credentials.userId}
|
||||
|
|
|
@ -82,7 +82,8 @@ const Pill = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
async componentWillReceiveProps(nextProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
async UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
let resourceId;
|
||||
let prefix;
|
||||
|
||||
|
@ -155,10 +156,12 @@ const Pill = createReactClass({
|
|||
this.setState({resourceId, pillType, member, group, room});
|
||||
},
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
this._unmounted = false;
|
||||
this._matrixClient = MatrixClientPeg.get();
|
||||
this.componentWillReceiveProps(this.props);
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
this.UNSAFE_componentWillReceiveProps(this.props); // HACK: We shouldn't be calling lifecycle functions ourselves.
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
|
@ -62,11 +62,13 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
// TODO: [REACT-WARNING] Move this to class constructor
|
||||
this._initStateFromProps(this.props);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps: function(newProps) {
|
||||
this._initStateFromProps(newProps);
|
||||
},
|
||||
|
||||
|
@ -132,7 +134,7 @@ export default createReactClass({
|
|||
const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label;
|
||||
if (this.state.custom) {
|
||||
picker = (
|
||||
<Field id={`powerSelector_custom_${this.props.powerLevelKey}`} type="number"
|
||||
<Field type="number"
|
||||
label={label} max={this.props.maxValue}
|
||||
onBlur={this.onCustomBlur} onKeyDown={this.onCustomKeyDown} onChange={this.onCustomChange}
|
||||
value={String(this.state.customValue)} disabled={this.props.disabled} />
|
||||
|
@ -151,7 +153,7 @@ export default createReactClass({
|
|||
});
|
||||
|
||||
picker = (
|
||||
<Field id={`powerSelector_notCustom_${this.props.powerLevelKey}`} element="select"
|
||||
<Field element="select"
|
||||
label={label} onChange={this.onSelectChange}
|
||||
value={String(this.state.selectValue)} disabled={this.props.disabled}>
|
||||
{options}
|
||||
|
|
|
@ -184,7 +184,7 @@ export default class ReplyThread extends React.Component {
|
|||
ref={ref} permalinkCreator={permalinkCreator} />;
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
this.unmounted = false;
|
||||
this.room = this.context.getRoom(this.props.parentEv.getRoomId());
|
||||
this.room.on("Room.redaction", this.onRoomRedaction);
|
||||
|
|
|
@ -23,7 +23,6 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
|||
// Controlled form component wrapping Field for inputting a room alias scoped to a given domain
|
||||
export default class RoomAliasField extends React.PureComponent {
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string.isRequired,
|
||||
|
@ -50,7 +49,6 @@ export default class RoomAliasField extends React.PureComponent {
|
|||
className="mx_RoomAliasField"
|
||||
prefix={poundSign}
|
||||
postfix={domain}
|
||||
id={this.props.id}
|
||||
ref={ref => this._fieldRef = ref}
|
||||
onValidate={this._onValidate}
|
||||
placeholder={_t("e.g. my-room")}
|
||||
|
|
|
@ -31,7 +31,6 @@ export default class SyntaxHighlight extends React.Component {
|
|||
}
|
||||
|
||||
// componentDidUpdate used here for reusability
|
||||
// componentWillReceiveProps fires too early to call highlightBlock on.
|
||||
componentDidUpdate() {
|
||||
if (this._el) highlightBlock(this._el);
|
||||
}
|
||||
|
|
|
@ -36,11 +36,9 @@ const TintableSvg = createReactClass({
|
|||
idSequence: 0,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.fixups = [];
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.fixups = [];
|
||||
|
||||
this.id = TintableSvg.idSequence++;
|
||||
TintableSvg.mounts[this.id] = this;
|
||||
},
|
||||
|
|
|
@ -35,6 +35,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._user_id_input = createRef();
|
||||
},
|
||||
|
|
|
@ -17,95 +17,17 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {replaceableComponent} from "../../../../utils/replaceableComponent";
|
||||
import {MatrixClientPeg} from "../../../../MatrixClientPeg";
|
||||
import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
import {ToDeviceChannel} from "matrix-js-sdk/src/crypto/verification/request/ToDeviceChannel";
|
||||
import {decodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
||||
import Spinner from "../Spinner";
|
||||
import * as QRCode from "qrcode";
|
||||
|
||||
const CODE_VERSION = 0x02; // the version of binary QR codes we support
|
||||
const BINARY_PREFIX = "MATRIX"; // ASCII, used to prefix the binary format
|
||||
const MODE_VERIFY_OTHER_USER = 0x00; // Verifying someone who isn't us
|
||||
const MODE_VERIFY_SELF_TRUSTED = 0x01; // We trust the master key
|
||||
const MODE_VERIFY_SELF_UNTRUSTED = 0x02; // We do not trust the master key
|
||||
|
||||
@replaceableComponent("views.elements.crypto.VerificationQRCode")
|
||||
export default class VerificationQRCode extends React.PureComponent {
|
||||
static propTypes = {
|
||||
prefix: PropTypes.string.isRequired,
|
||||
version: PropTypes.number.isRequired,
|
||||
mode: PropTypes.number.isRequired,
|
||||
transactionId: PropTypes.string.isRequired, // or requestEventId
|
||||
firstKeyB64: PropTypes.string.isRequired,
|
||||
secondKeyB64: PropTypes.string.isRequired,
|
||||
secretB64: PropTypes.string.isRequired,
|
||||
qrCodeData: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static async getPropsForRequest(verificationRequest: VerificationRequest) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const myUserId = cli.getUserId();
|
||||
const otherUserId = verificationRequest.otherUserId;
|
||||
|
||||
let mode = MODE_VERIFY_OTHER_USER;
|
||||
if (myUserId === otherUserId) {
|
||||
// Mode changes depending on whether or not we trust the master cross signing key
|
||||
const myTrust = cli.checkUserTrust(myUserId);
|
||||
if (myTrust.isCrossSigningVerified()) {
|
||||
mode = MODE_VERIFY_SELF_TRUSTED;
|
||||
} else {
|
||||
mode = MODE_VERIFY_SELF_UNTRUSTED;
|
||||
}
|
||||
}
|
||||
|
||||
const requestEvent = verificationRequest.requestEvent;
|
||||
const transactionId = requestEvent.getId()
|
||||
? requestEvent.getId()
|
||||
: ToDeviceChannel.getTransactionId(requestEvent);
|
||||
|
||||
const qrProps = {
|
||||
prefix: BINARY_PREFIX,
|
||||
version: CODE_VERSION,
|
||||
mode,
|
||||
transactionId,
|
||||
firstKeyB64: '', // worked out shortly
|
||||
secondKeyB64: '', // worked out shortly
|
||||
secretB64: verificationRequest.encodedSharedSecret,
|
||||
};
|
||||
|
||||
const myCrossSigningInfo = cli.getStoredCrossSigningForUser(myUserId);
|
||||
const myDevices = (await cli.getStoredDevicesForUser(myUserId)) || [];
|
||||
|
||||
if (mode === MODE_VERIFY_OTHER_USER) {
|
||||
// First key is our master cross signing key
|
||||
qrProps.firstKeyB64 = myCrossSigningInfo.getId("master");
|
||||
|
||||
// Second key is the other user's master cross signing key
|
||||
const otherUserCrossSigningInfo = cli.getStoredCrossSigningForUser(otherUserId);
|
||||
qrProps.secondKeyB64 = otherUserCrossSigningInfo.getId("master");
|
||||
} else if (mode === MODE_VERIFY_SELF_TRUSTED) {
|
||||
// First key is our master cross signing key
|
||||
qrProps.firstKeyB64 = myCrossSigningInfo.getId("master");
|
||||
|
||||
// Second key is the other device's device key
|
||||
const otherDevice = verificationRequest.targetDevice;
|
||||
const otherDeviceId = otherDevice ? otherDevice.deviceId : null;
|
||||
const device = myDevices.find(d => d.deviceId === otherDeviceId);
|
||||
qrProps.secondKeyB64 = device.getFingerprint();
|
||||
} else if (mode === MODE_VERIFY_SELF_UNTRUSTED) {
|
||||
// First key is our device's key
|
||||
qrProps.firstKeyB64 = cli.getDeviceEd25519Key();
|
||||
|
||||
// Second key is what we think our master cross signing key is
|
||||
qrProps.secondKeyB64 = myCrossSigningInfo.getId("master");
|
||||
}
|
||||
|
||||
return qrProps;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
dataUri: null,
|
||||
};
|
||||
|
@ -119,39 +41,8 @@ export default class VerificationQRCode extends React.PureComponent {
|
|||
}
|
||||
|
||||
async generateQrCode() {
|
||||
let buf = Buffer.alloc(0); // we'll concat our way through life
|
||||
|
||||
const appendByte = (b: number) => {
|
||||
const tmpBuf = Buffer.from([b]);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
};
|
||||
const appendInt = (i: number) => {
|
||||
const tmpBuf = Buffer.alloc(2);
|
||||
tmpBuf.writeInt16BE(i, 0);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
};
|
||||
const appendStr = (s: string, enc: string, withLengthPrefix = true) => {
|
||||
const tmpBuf = Buffer.from(s, enc);
|
||||
if (withLengthPrefix) appendInt(tmpBuf.byteLength);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
};
|
||||
const appendEncBase64 = (b64: string) => {
|
||||
const b = decodeBase64(b64);
|
||||
const tmpBuf = Buffer.from(b);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
};
|
||||
|
||||
// Actually build the buffer for the QR code
|
||||
appendStr(this.props.prefix, "ascii", false);
|
||||
appendByte(this.props.version);
|
||||
appendByte(this.props.mode);
|
||||
appendStr(this.props.transactionId, "utf-8");
|
||||
appendEncBase64(this.props.firstKeyB64);
|
||||
appendEncBase64(this.props.secondKeyB64);
|
||||
appendEncBase64(this.props.secretB64);
|
||||
|
||||
// Now actually assemble the QR code's data URI
|
||||
const uri = await QRCode.toDataURL([{data: buf, mode: 'byte'}], {
|
||||
const uri = await QRCode.toDataURL([{data: this.props.qrCodeData.buffer, mode: 'byte'}], {
|
||||
errorCorrectionLevel: 'L', // we want it as trivial-looking as possible
|
||||
});
|
||||
this.setState({dataUri: uri});
|
||||
|
|
|
@ -22,6 +22,7 @@ import { _t } from '../../../languageHandler';
|
|||
|
||||
import * as recent from '../../../emojipicker/recent';
|
||||
import {DATA_BY_CATEGORY, getEmojiFromUnicode} from "../../../emoji";
|
||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||
|
||||
export const CATEGORY_HEADER_HEIGHT = 22;
|
||||
export const EMOJI_HEIGHT = 37;
|
||||
|
@ -214,7 +215,7 @@ class EmojiPicker extends React.Component {
|
|||
<div className="mx_EmojiPicker">
|
||||
<Header categories={this.categories} defaultCategory="recent" onAnchorClick={this.scrollToCategory} />
|
||||
<Search query={this.state.filter} onChange={this.onChangeFilter} />
|
||||
<div className="mx_EmojiPicker_body" ref={this.bodyRef} onScroll={this.onScroll}>
|
||||
<AutoHideScrollbar className="mx_EmojiPicker_body" wrappedRef={e => this.bodyRef.current = e} onScroll={this.onScroll}>
|
||||
{this.categories.map(category => {
|
||||
const emojis = this.memoizedDataByCategory[category.id];
|
||||
const categoryElement = (<Category key={category.id} id={category.id} name={category.name}
|
||||
|
@ -226,7 +227,7 @@ class EmojiPicker extends React.Component {
|
|||
heightBefore += height;
|
||||
return categoryElement;
|
||||
})}
|
||||
</div>
|
||||
</AutoHideScrollbar>
|
||||
{this.state.previewEmoji || !this.props.showQuickReactions
|
||||
? <Preview emoji={this.state.previewEmoji} />
|
||||
: <QuickReactions onClick={this.onClickEmoji} selectedEmojis={this.props.selectedEmojis} /> }
|
||||
|
|
|
@ -49,12 +49,13 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this._unmounted = false;
|
||||
this._initGroupStore(this.props.groupId);
|
||||
},
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
if (newProps.groupId !== this.props.groupId) {
|
||||
this._unregisterGroupStore(this.props.groupId);
|
||||
this._initGroupStore(newProps.groupId);
|
||||
|
|
|
@ -47,7 +47,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this._unmounted = false;
|
||||
this._initGroupStore(this.props.groupId);
|
||||
},
|
||||
|
|
|
@ -36,7 +36,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this._initGroupStore(this.props.groupId);
|
||||
},
|
||||
|
||||
|
|
|
@ -47,11 +47,12 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this._initGroupStore(this.props.groupId);
|
||||
},
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
if (newProps.groupId !== this.props.groupId) {
|
||||
this._unregisterGroupStore(this.props.groupId);
|
||||
this._initGroupStore(newProps.groupId);
|
||||
|
|
|
@ -39,7 +39,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this._unmounted = false;
|
||||
this._initGroupStore(this.props.groupId);
|
||||
},
|
||||
|
|
|
@ -55,7 +55,7 @@ const GroupTile = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => {
|
||||
this.setState({profile});
|
||||
}).catch((err) => {
|
||||
|
|
|
@ -34,7 +34,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
this.context.getJoinedGroups().then((result) => {
|
||||
this.setState({groups: result.groups || [], error: null});
|
||||
}, (err) => {
|
||||
|
|
|
@ -170,6 +170,7 @@ export default createReactClass({
|
|||
return MatrixClientPeg.get().mxcUrlToHttp(content.url);
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._iframe = createRef();
|
||||
this._dummyLink = createRef();
|
||||
|
|
|
@ -67,11 +67,6 @@ export default class MImageBody extends React.Component {
|
|||
this._image = createRef();
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.unmounted = false;
|
||||
this.context.on('sync', this.onClientSync);
|
||||
}
|
||||
|
||||
// FIXME: factor this out and aplpy it to MVideoBody and MAudioBody too!
|
||||
onClientSync(syncState, prevState) {
|
||||
if (this.unmounted) return;
|
||||
|
@ -258,6 +253,9 @@ export default class MImageBody extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unmounted = false;
|
||||
this.context.on('sync', this.onClientSync);
|
||||
|
||||
const content = this.props.mxEvent.getContent();
|
||||
if (content.file !== undefined && this.state.decryptedUrl === null) {
|
||||
let thumbnailPromise = Promise.resolve(null);
|
||||
|
|
|
@ -47,6 +47,7 @@ export default createReactClass({
|
|||
maxImageHeight: PropTypes.number,
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._body = createRef();
|
||||
},
|
||||
|
|
|
@ -42,7 +42,7 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
this.unmounted = false;
|
||||
this._updateRelatedGroups();
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ export default createReactClass({
|
|||
return successful;
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._content = createRef();
|
||||
},
|
||||
|
|
|
@ -28,14 +28,26 @@ export const PendingActionSpinner = ({text}) => {
|
|||
</div>;
|
||||
};
|
||||
|
||||
const EncryptionInfo = ({waitingForOtherParty, waitingForNetwork, member, onStartVerification}) => {
|
||||
const EncryptionInfo = ({
|
||||
waitingForOtherParty,
|
||||
waitingForNetwork,
|
||||
member,
|
||||
onStartVerification,
|
||||
isRoomEncrypted,
|
||||
inDialog,
|
||||
isSelfVerification,
|
||||
}) => {
|
||||
let content;
|
||||
if (waitingForOtherParty || waitingForNetwork) {
|
||||
let text;
|
||||
if (waitingForOtherParty) {
|
||||
text = _t("Waiting for %(displayName)s to accept…", {
|
||||
displayName: member.displayName || member.name || member.userId,
|
||||
});
|
||||
if (isSelfVerification) {
|
||||
text = _t("Waiting for you to accept on your other session…");
|
||||
} else {
|
||||
text = _t("Waiting for %(displayName)s to accept…", {
|
||||
displayName: member.displayName || member.name || member.userId,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
text = _t("Accepting…");
|
||||
}
|
||||
|
@ -49,13 +61,31 @@ const EncryptionInfo = ({waitingForOtherParty, waitingForNetwork, member, onStar
|
|||
);
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<div className="mx_UserInfo_container">
|
||||
<h3>{_t("Encryption")}</h3>
|
||||
let description;
|
||||
if (isRoomEncrypted) {
|
||||
description = (
|
||||
<div>
|
||||
<p>{_t("Messages in this room are end-to-end encrypted.")}</p>
|
||||
<p>{_t("Your messages are secured and only you and the recipient have the unique keys to unlock them.")}</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
description = (
|
||||
<div>
|
||||
<p>{_t("Messages in this room are not end-to-end encrypted.")}</p>
|
||||
<p>{_t("In encrypted rooms, your messages are secured and only you and the recipient have the unique keys to unlock them.")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (inDialog) {
|
||||
return content;
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<div className="mx_UserInfo_container">
|
||||
<h3>{_t("Encryption")}</h3>
|
||||
{ description }
|
||||
</div>
|
||||
<div className="mx_UserInfo_container">
|
||||
<h3>{_t("Verify User")}</h3>
|
||||
|
|
|
@ -22,6 +22,7 @@ import VerificationPanel from "./VerificationPanel";
|
|||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import {ensureDMExists} from "../../../createRoom";
|
||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||
import {useAsyncMemo} from "../../../hooks/useAsyncMemo";
|
||||
import Modal from "../../../Modal";
|
||||
import {PHASE_REQUESTED, PHASE_UNSENT} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
import * as sdk from "../../../index";
|
||||
|
@ -30,7 +31,8 @@ import {_t} from "../../../languageHandler";
|
|||
// cancellation codes which constitute a key mismatch
|
||||
const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"];
|
||||
|
||||
const EncryptionPanel = ({verificationRequest, verificationRequestPromise, member, onClose, layout}) => {
|
||||
const EncryptionPanel = (props) => {
|
||||
const {verificationRequest, verificationRequestPromise, member, onClose, layout, isRoomEncrypted, inDialog} = props;
|
||||
const [request, setRequest] = useState(verificationRequest);
|
||||
// state to show a spinner immediately after clicking "start verification",
|
||||
// before we have a request
|
||||
|
@ -44,6 +46,12 @@ const EncryptionPanel = ({verificationRequest, verificationRequestPromise, membe
|
|||
}
|
||||
}, [verificationRequest]);
|
||||
|
||||
const deviceId = request && request.channel.deviceId;
|
||||
const device = useAsyncMemo(() => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
return cli.getStoredDevice(cli.getUserId(), deviceId);
|
||||
}, [deviceId]);
|
||||
|
||||
useEffect(() => {
|
||||
async function awaitPromise() {
|
||||
setRequesting(true);
|
||||
|
@ -83,6 +91,22 @@ const EncryptionPanel = ({verificationRequest, verificationRequestPromise, membe
|
|||
}, [onClose, request]);
|
||||
useEventEmitter(request, "change", changeHandler);
|
||||
|
||||
const onCancel = useCallback(function() {
|
||||
if (request) {
|
||||
request.cancel();
|
||||
}
|
||||
}, [request]);
|
||||
|
||||
let cancelButton;
|
||||
if (layout !== "dialog" && request && request.pending) {
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
cancelButton = (<AccessibleButton
|
||||
className="mx_EncryptionPanel_cancel"
|
||||
onClick={onCancel}
|
||||
title={_t('Cancel')}
|
||||
></AccessibleButton>);
|
||||
}
|
||||
|
||||
const onStartVerification = useCallback(async () => {
|
||||
setRequesting(true);
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
@ -95,23 +119,36 @@ const EncryptionPanel = ({verificationRequest, verificationRequestPromise, membe
|
|||
const requested =
|
||||
(!request && isRequesting) ||
|
||||
(request && (phase === PHASE_REQUESTED || phase === PHASE_UNSENT || phase === undefined));
|
||||
const isSelfVerification = request ?
|
||||
request.isSelfVerification :
|
||||
member.userId === MatrixClientPeg.get().getUserId();
|
||||
if (!request || requested) {
|
||||
const initiatedByMe = (!request && isRequesting) || (request && request.initiatedByMe);
|
||||
return <EncryptionInfo
|
||||
onStartVerification={onStartVerification}
|
||||
member={member}
|
||||
waitingForOtherParty={requested && initiatedByMe}
|
||||
waitingForNetwork={requested && !initiatedByMe} />;
|
||||
return (<React.Fragment>
|
||||
{cancelButton}
|
||||
<EncryptionInfo
|
||||
isRoomEncrypted={isRoomEncrypted}
|
||||
onStartVerification={onStartVerification}
|
||||
member={member}
|
||||
isSelfVerification={isSelfVerification}
|
||||
waitingForOtherParty={requested && initiatedByMe}
|
||||
waitingForNetwork={requested && !initiatedByMe}
|
||||
inDialog={inDialog} />
|
||||
</React.Fragment>);
|
||||
} else {
|
||||
return (
|
||||
return (<React.Fragment>
|
||||
{cancelButton}
|
||||
<VerificationPanel
|
||||
isRoomEncrypted={isRoomEncrypted}
|
||||
layout={layout}
|
||||
onClose={onClose}
|
||||
member={member}
|
||||
request={request}
|
||||
key={request.channel.transactionId}
|
||||
phase={phase} />
|
||||
);
|
||||
inDialog={inDialog}
|
||||
phase={phase}
|
||||
device={device} />
|
||||
</React.Fragment>);
|
||||
}
|
||||
};
|
||||
EncryptionPanel.propTypes = {
|
||||
|
@ -119,6 +156,7 @@ EncryptionPanel.propTypes = {
|
|||
onClose: PropTypes.func.isRequired,
|
||||
verificationRequest: PropTypes.object,
|
||||
layout: PropTypes.string,
|
||||
inDialog: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default EncryptionPanel;
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class HeaderButtons extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
this._storeToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelUpdate.bind(this));
|
||||
this._dispatcherRef = dis.register(this.onAction.bind(this)); // used by subclasses
|
||||
}
|
||||
|
|
|
@ -68,8 +68,10 @@ export const getE2EStatus = (cli, userId, devices) => {
|
|||
return hasUnverifiedDevice ? "warning" : "verified";
|
||||
}
|
||||
const isMe = userId === cli.getUserId();
|
||||
const userVerified = cli.checkUserTrust(userId).isCrossSigningVerified();
|
||||
if (!userVerified) return "normal";
|
||||
const userTrust = cli.checkUserTrust(userId);
|
||||
if (!userTrust.isCrossSigningVerified()) {
|
||||
return userTrust.wasCrossSigningVerified() ? "warning" : "normal";
|
||||
}
|
||||
|
||||
const anyDeviceUnverified = devices.some(device => {
|
||||
const { deviceId } = device;
|
||||
|
@ -1297,8 +1299,7 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
|||
const userVerified = userTrust.isCrossSigningVerified();
|
||||
const isMe = member.userId === cli.getUserId();
|
||||
const canVerify = SettingsStore.isFeatureEnabled("feature_cross_signing") &&
|
||||
homeserverSupportsCrossSigning &&
|
||||
isRoomEncrypted && !userVerified && !isMe;
|
||||
homeserverSupportsCrossSigning && !userVerified && !isMe;
|
||||
|
||||
const setUpdating = (updating) => {
|
||||
setPendingUpdateCount(count => count + (updating ? 1 : -1));
|
||||
|
@ -1320,20 +1321,15 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
|||
);
|
||||
}
|
||||
|
||||
let devicesSection;
|
||||
if (isRoomEncrypted) {
|
||||
devicesSection = <DevicesSection
|
||||
loading={devices === undefined}
|
||||
devices={devices}
|
||||
userId={member.userId} />;
|
||||
}
|
||||
|
||||
const securitySection = (
|
||||
<div className="mx_UserInfo_container">
|
||||
<h3>{ _t("Security") }</h3>
|
||||
<p>{ text }</p>
|
||||
{ verifyButton }
|
||||
{ devicesSection }
|
||||
<DevicesSection
|
||||
loading={devices === undefined}
|
||||
devices={devices}
|
||||
userId={member.userId} />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -1388,6 +1384,7 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => {
|
|||
<div>
|
||||
<div>
|
||||
<MemberAvatar
|
||||
key={member.userId} // to instantly blank the avatar when UserInfo changes members
|
||||
member={member}
|
||||
width={2 * 0.3 * window.innerHeight} // 2x@30vh
|
||||
height={2 * 0.3 * window.innerHeight} // 2x@30vh
|
||||
|
@ -1447,9 +1444,11 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => {
|
|||
<div className="mx_UserInfo_container mx_UserInfo_separator">
|
||||
<div className="mx_UserInfo_profile">
|
||||
<div>
|
||||
<h2 aria-label={displayName}>
|
||||
<h2>
|
||||
{ e2eIcon }
|
||||
{ displayName }
|
||||
<span title={displayName} aria-label={displayName}>
|
||||
{ displayName }
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div>{ member.userId }</div>
|
||||
|
@ -1496,7 +1495,7 @@ const UserInfo = ({user, groupId, roomId, onClose, phase=RIGHT_PANEL_PHASES.Room
|
|||
case RIGHT_PANEL_PHASES.EncryptionPanel:
|
||||
classes.push("mx_UserInfo_smallAvatar");
|
||||
content = (
|
||||
<EncryptionPanel {...props} member={member} onClose={onClose} />
|
||||
<EncryptionPanel {...props} member={member} onClose={onClose} isRoomEncrypted={isRoomEncrypted} />
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue