Merge branch 'develop' into travis/react-warnings/3-componentCannotDidMount

This commit is contained in:
Travis Ralston 2020-04-01 11:14:19 -06:00 committed by GitHub
commit 07030a9466
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
102 changed files with 640 additions and 294 deletions

View file

@ -1,3 +1,14 @@
Changes in [2.3.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.1) (2020-04-01)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.3.0...v2.3.1)
* Fix jitsi popout URL
[\#4327](https://github.com/matrix-org/matrix-react-sdk/pull/4327)
* Remove underscore from Jitsi conference names
[\#4324](https://github.com/matrix-org/matrix-react-sdk/pull/4324)
* Fix popout support for jitsi widgets
[\#4322](https://github.com/matrix-org/matrix-react-sdk/pull/4322)
Changes in [2.3.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.0) (2020-03-30) Changes in [2.3.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.0) (2020-03-30)
=================================================================================================== ===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.3.0-rc.1...v2.3.0) [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.3.0-rc.1...v2.3.0)

View file

@ -51,6 +51,17 @@ Settings are the different options a user may set or experience in the applicati
} }
``` ```
Settings that support the config level can be set in the config file under the `settingDefaults` key (note that some settings, like the "theme" setting, are special cased in the config file):
```json
{
...
"settingDefaults": {
"settingName": true
},
...
}
```
### Getting values for a setting ### Getting values for a setting
After importing `SettingsStore`, simply make a call to `SettingsStore.getValue`. The `roomId` parameter should always After importing `SettingsStore`, simply make a call to `SettingsStore.getValue`. The `roomId` parameter should always

View file

@ -1,6 +1,6 @@
{ {
"name": "matrix-react-sdk", "name": "matrix-react-sdk",
"version": "2.3.0", "version": "2.3.1",
"description": "SDK for matrix.org using React", "description": "SDK for matrix.org using React",
"author": "matrix.org", "author": "matrix.org",
"repository": { "repository": {

View file

@ -60,3 +60,14 @@ limitations under the License.
.mx_InteractiveAuthEntryComponents_passwordSection { .mx_InteractiveAuthEntryComponents_passwordSection {
width: 300px; width: 300px;
} }
.mx_InteractiveAuthEntryComponents_sso_buttons {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 20px;
.mx_AccessibleButton {
margin-left: 5px;
}
}

View file

@ -36,12 +36,20 @@ limitations under the License.
font-weight: 600; font-weight: 600;
} }
.mx_AccessibleButton_kind_primary_outline {
color: $button-primary-bg-color;
background-color: $button-secondary-bg-color;
border: 1px solid $button-primary-bg-color;
font-weight: 600;
}
.mx_AccessibleButton_kind_secondary { .mx_AccessibleButton_kind_secondary {
color: $accent-color; color: $accent-color;
font-weight: 600; font-weight: 600;
} }
.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled { .mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled,
.mx_AccessibleButton_kind_primary_outline.mx_AccessibleButton_disabled {
opacity: 0.4; opacity: 0.4;
} }
@ -60,7 +68,14 @@ limitations under the License.
background-color: $button-danger-bg-color; background-color: $button-danger-bg-color;
} }
.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled { .mx_AccessibleButton_kind_danger_outline {
color: $button-danger-bg-color;
background-color: $button-secondary-bg-color;
border: 1px solid $button-danger-bg-color;
}
.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled,
.mx_AccessibleButton_kind_danger_outline.mx_AccessibleButton_disabled {
color: $button-danger-disabled-fg-color; color: $button-danger-disabled-fg-color;
background-color: $button-danger-disabled-bg-color; background-color: $button-danger-disabled-bg-color;
} }

View file

@ -266,12 +266,31 @@ limitations under the License.
} }
} }
.mx_AccessibleButton {
padding: 8px 18px;
&.mx_AccessibleButton_kind_primary {
color: $accent-color;
background-color: $accent-bg-color;
}
&.mx_AccessibleButton_kind_danger {
color: $notice-primary-color;
background-color: $notice-primary-bg-color;
}
}
.mx_VerificationShowSas .mx_AccessibleButton,
.mx_UserInfo_wideButton { .mx_UserInfo_wideButton {
display: block; display: block;
margin: 16px 0; margin: 16px 0 8px;
} }
button.mx_UserInfo_wideButton {
width: 100%; // FIXME get rid of this once we get rid of DialogButtons here
.mx_VerificationShowSas {
.mx_AccessibleButton + .mx_AccessibleButton {
margin: 8px 0; // space between buttons
}
} }
} }

View file

@ -21,6 +21,7 @@ import * as sdk from './index';
import Modal from './Modal'; import Modal from './Modal';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import IdentityAuthClient from './IdentityAuthClient'; import IdentityAuthClient from './IdentityAuthClient';
import {SSOAuthEntry} from "./components/views/auth/InteractiveAuthEntryComponents";
function getIdServerDomain() { function getIdServerDomain() {
return MatrixClientPeg.get().idBaseUrl.split("://")[1]; return MatrixClientPeg.get().idBaseUrl.split("://")[1];
@ -188,11 +189,31 @@ export default class AddThreepid {
// pop up an interactive auth dialog // pop up an interactive auth dialog
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
const dialogAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("Use Single Sign On to continue"),
body: _t("Confirm adding this email address by using " +
"Single Sign On to prove your identity."),
continueText: _t("Single Sign On"),
continueKind: "primary",
},
[SSOAuthEntry.PHASE_POSTAUTH]: {
title: _t("Confirm adding email"),
body: _t("Click the button below to confirm adding this email address."),
continueText: _t("Confirm"),
continueKind: "primary",
},
};
const { finished } = Modal.createTrackedDialog('Add Email', '', InteractiveAuthDialog, { const { finished } = Modal.createTrackedDialog('Add Email', '', InteractiveAuthDialog, {
title: _t("Add Email Address"), title: _t("Add Email Address"),
matrixClient: MatrixClientPeg.get(), matrixClient: MatrixClientPeg.get(),
authData: e.data, authData: e.data,
makeRequest: this._makeAddThreepidOnlyRequest, makeRequest: this._makeAddThreepidOnlyRequest,
aestheticsForStagePhases: {
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
},
}); });
return finished; return finished;
} }
@ -285,11 +306,30 @@ export default class AddThreepid {
// pop up an interactive auth dialog // pop up an interactive auth dialog
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
const dialogAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("Use Single Sign On to continue"),
body: _t("Confirm adding this phone number by using " +
"Single Sign On to prove your identity."),
continueText: _t("Single Sign On"),
continueKind: "primary",
},
[SSOAuthEntry.PHASE_POSTAUTH]: {
title: _t("Confirm adding phone number"),
body: _t("Click the button below to confirm adding this phone number."),
continueText: _t("Confirm"),
continueKind: "primary",
},
};
const { finished } = Modal.createTrackedDialog('Add MSISDN', '', InteractiveAuthDialog, { const { finished } = Modal.createTrackedDialog('Add MSISDN', '', InteractiveAuthDialog, {
title: _t("Add Phone Number"), title: _t("Add Phone Number"),
matrixClient: MatrixClientPeg.get(), matrixClient: MatrixClientPeg.get(),
authData: e.data, authData: e.data,
makeRequest: this._makeAddThreepidOnlyRequest, makeRequest: this._makeAddThreepidOnlyRequest,
aestheticsForStagePhases: {
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
},
}); });
return finished; return finished;
} }

View file

@ -38,7 +38,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._unmounted = false; this._unmounted = false;
// XXX: temporary logging to try to diagnose // XXX: temporary logging to try to diagnose
// https://github.com/vector-im/riot-web/issues/3148 // https://github.com/vector-im/riot-web/issues/3148

View file

@ -24,8 +24,7 @@ import {MatrixClientPeg} from "./MatrixClientPeg";
import RoomViewStore from "./stores/RoomViewStore"; import RoomViewStore from "./stores/RoomViewStore";
import {IntegrationManagers} from "./integrations/IntegrationManagers"; import {IntegrationManagers} from "./integrations/IntegrationManagers";
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import {Capability, KnownWidgetActions} from "./widgets/WidgetApi"; import {Capability} from "./widgets/WidgetApi";
import SdkConfig from "./SdkConfig";
const WIDGET_API_VERSION = '0.0.2'; // Current API version const WIDGET_API_VERSION = '0.0.2'; // Current API version
const SUPPORTED_WIDGET_API_VERSIONS = [ const SUPPORTED_WIDGET_API_VERSIONS = [
@ -220,13 +219,6 @@ export default class FromWidgetPostMessageApi {
} }
} else if (action === 'get_openid') { } else if (action === 'get_openid') {
// Handled by caller // Handled by caller
} else if (action === KnownWidgetActions.GetRiotWebConfig) {
if (ActiveWidgetStore.widgetHasCapability(widgetId, Capability.GetRiotWebConfig)) {
this.sendResponse(event, {
api: INBOUND_API_NAME,
config: SdkConfig.get(),
});
}
} else { } else {
console.warn('Widget postMessage event unhandled'); console.warn('Widget postMessage event unhandled');
this.sendError(event, {message: 'The postMessage was unhandled'}); this.sendError(event, {message: 'The postMessage was unhandled'});

View file

@ -30,8 +30,6 @@ export const DEFAULTS: ConfigOptions = {
jitsi: { jitsi: {
// Default conference domain // Default conference domain
preferredDomain: "jitsi.riot.im", preferredDomain: "jitsi.riot.im",
// Default Jitsi Meet API location
externalApiUrl: "https://jitsi.riot.im/libs/external_api.min.js",
}, },
}; };

View file

@ -412,17 +412,20 @@ export const Commands = [
button: _t("Continue"), button: _t("Continue"),
}, },
)); ));
finished = finished.then(([useDefault]: any) => {
if (useDefault) {
useDefaultIdentityServer();
return;
}
throw new Error(_t("Use an identity server to invite by email. Manage in Settings."));
});
} else { } else {
return reject(_t("Use an identity server to invite by email. Manage in Settings.")); return reject(_t("Use an identity server to invite by email. Manage in Settings."));
} }
} }
const inviter = new MultiInviter(roomId); const inviter = new MultiInviter(roomId);
return success(finished.then(([useDefault]: any) => { return success(finished.then(() => {
if (useDefault) {
useDefaultIdentityServer();
} else if (useDefault === false) {
throw new Error(_t("Use an identity server to invite by email. Manage in Settings."));
}
return inviter.invite([address]); return inviter.invite([address]);
}).then(() => { }).then(() => {
if (inviter.getCompletionState(address) !== "invited") { if (inviter.getCompletionState(address) !== "invited") {

View file

@ -37,7 +37,7 @@ export default createReactClass({
return { device: null }; return { device: null };
}, },
componentWillMount: function() { componentDidMount: function() {
this._unmounted = false; this._unmounted = false;
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
@ -79,7 +79,7 @@ export default createReactClass({
}, },
onDeviceVerificationChanged: function(userId, device) { onDeviceVerificationChanged: function(userId, device) {
if (userId == this.props.event.getSender()) { if (userId === this.props.event.getSender()) {
this.refreshDevice().then((dev) => { this.refreshDevice().then((dev) => {
this.setState({ device: dev }); this.setState({ device: dev });
}); });

View file

@ -30,7 +30,7 @@ import EventIndexPeg from "../../../../indexing/EventIndexPeg";
export default class ManageEventIndexDialog extends React.Component { export default class ManageEventIndexDialog extends React.Component {
static propTypes = { static propTypes = {
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
} };
constructor(props) { constructor(props) {
super(props); super(props);
@ -82,7 +82,7 @@ export default class ManageEventIndexDialog extends React.Component {
} }
} }
async componentWillMount(): void { async componentDidMount(): void {
let eventIndexSize = 0; let eventIndexSize = 0;
let crawlingRoomsCount = 0; let crawlingRoomsCount = 0;
let roomCount = 0; let roomCount = 0;
@ -126,16 +126,12 @@ export default class ManageEventIndexDialog extends React.Component {
import("./DisableEventIndexDialog"), import("./DisableEventIndexDialog"),
null, null, /* priority = */ false, /* static = */ true, null, null, /* priority = */ false, /* static = */ true,
); );
} };
_onDone = () => {
this.props.onFinished(true);
}
_onCrawlerSleepTimeChange = (e) => { _onCrawlerSleepTimeChange = (e) => {
this.setState({crawlerSleepTime: e.target.value}); this.setState({crawlerSleepTime: e.target.value});
SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value);
} };
render() { render() {
let crawlerState; let crawlerState;

View file

@ -30,7 +30,7 @@ class CustomRoomTagPanel extends React.Component {
}; };
} }
componentWillMount() { componentDidMount() {
this._tagStoreToken = CustomRoomTagStore.addListener(() => { this._tagStoreToken = CustomRoomTagStore.addListener(() => {
this.setState({tags: CustomRoomTagStore.getSortedTags()}); this.setState({tags: CustomRoomTagStore.getSortedTags()});
}); });

View file

@ -58,7 +58,7 @@ export default class EmbeddedPage extends React.PureComponent {
return sanitizeHtml(_t(s)); return sanitizeHtml(_t(s));
} }
componentWillMount() { componentDidMount() {
this._unmounted = false; this._unmounted = false;
if (!this.props.url) { if (!this.props.url) {

View file

@ -428,12 +428,11 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._unmounted = false; this._unmounted = false;
this._matrixClient = MatrixClientPeg.get(); this._matrixClient = MatrixClientPeg.get();
this._matrixClient.on("Group.myMembership", this._onGroupMyMembership); this._matrixClient.on("Group.myMembership", this._onGroupMyMembership);
this._changeAvatarComponent = null;
this._initGroupStore(this.props.groupId, true); this._initGroupStore(this.props.groupId, true);
this._dispatcherRef = dis.register(this._onAction); this._dispatcherRef = dis.register(this._onAction);

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2017 Vector Creations 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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'; import * as sdk from '../../index';
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
export default createReactClass({ export default createReactClass({
displayName: 'InteractiveAuth', displayName: 'InteractiveAuth',
@ -47,7 +49,7 @@ export default createReactClass({
// @param {bool} status True if the operation requiring // @param {bool} status True if the operation requiring
// auth was completed sucessfully, false if canceled. // auth was completed sucessfully, false if canceled.
// @param {object} result The result of the authenticated call // @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 // @param {object} extra Additional information about the UI Auth
// process: // process:
// * emailSid {string} If email auth was performed, the sid of // * 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 // is managed by some other party and should not be managed by
// the component itself. // the component itself.
continueIsManaged: PropTypes.bool, 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() { getInitialState: function() {
@ -205,6 +216,16 @@ export default createReactClass({
this._authLogic.submitAuthDict(authData); 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() { _renderCurrentStage: function() {
const stage = this.state.authStage; const stage = this.state.authStage;
if (!stage) { if (!stage) {
@ -233,6 +254,10 @@ export default createReactClass({
fail={this._onAuthStageFailed} fail={this._onAuthStageFailed}
setEmailSid={this._setEmailSid} setEmailSid={this._setEmailSid}
showContinue={!this.props.continueIsManaged} showContinue={!this.props.continueIsManaged}
onPhaseChange={this._onPhaseChange}
continueText={this.props.continueText}
continueKind={this.props.continueKind}
onCancel={this._onStageCancel}
/> />
); );
}, },

View file

@ -44,7 +44,7 @@ const LeftPanel = createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this.focusedElement = null; this.focusedElement = null;
this._breadcrumbsWatcherRef = SettingsStore.watchSetting( this._breadcrumbsWatcherRef = SettingsStore.watchSetting(

View file

@ -22,7 +22,7 @@ import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { DragDropContext } from 'react-beautiful-dnd'; import { DragDropContext } from 'react-beautiful-dnd';
import { Key, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard'; import {Key, isOnlyCtrlOrCmdKeyEvent, isOnlyCtrlOrCmdIgnoreShiftKeyEvent} from '../../Keyboard';
import PageTypes from '../../PageTypes'; import PageTypes from '../../PageTypes';
import CallMediaHandler from '../../CallMediaHandler'; import CallMediaHandler from '../../CallMediaHandler';
import { fixupColorFonts } from '../../utils/FontManager'; import { fixupColorFonts } from '../../utils/FontManager';
@ -381,7 +381,7 @@ const LoggedInView = createReactClass({
break; break;
case Key.SLASH: case Key.SLASH:
if (ctrlCmdOnly) { if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev)) {
KeyboardShortcuts.toggleDialog(); KeyboardShortcuts.toggleDialog();
handled = true; handled = true;
} }

View file

@ -221,7 +221,8 @@ export default createReactClass({
return {serverConfig: props}; return {serverConfig: props};
}, },
componentWillMount: function() { // TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
SdkConfig.put(this.props.config); SdkConfig.put(this.props.config);
// Used by _viewRoom before getting state from sync // Used by _viewRoom before getting state from sync
@ -261,9 +262,7 @@ export default createReactClass({
this._accountPassword = null; this._accountPassword = null;
this._accountPasswordTimer = null; this._accountPasswordTimer = null;
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
this._themeWatcher = new ThemeWatcher(); this._themeWatcher = new ThemeWatcher();
this._themeWatcher.start(); this._themeWatcher.start();

View file

@ -38,7 +38,7 @@ export default createReactClass({
contextType: MatrixClientContext, contextType: MatrixClientContext,
}, },
componentWillMount: function() { componentDidMount: function() {
this._fetch(); this._fetch();
}, },

View file

@ -108,7 +108,7 @@ export default class RightPanel extends React.Component {
} }
} }
componentWillMount() { componentDidMount() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
const cli = this.context; const cli = this.context;
cli.on("RoomState.members", this.onRoomStateMember); cli.on("RoomState.members", this.onRoomStateMember);

View file

@ -56,7 +56,8 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { // TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._unmounted = false; this._unmounted = false;
this.nextBatch = null; this.nextBatch = null;
this.filterTimeout = null; this.filterTimeout = null;
@ -89,9 +90,7 @@ export default createReactClass({
), ),
}); });
}); });
},
componentDidMount: function() {
this.refreshRoomList(); this.refreshRoomList();
}, },

View file

@ -96,7 +96,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
MatrixClientPeg.get().on("sync", this.onSyncStateChange); MatrixClientPeg.get().on("sync", this.onSyncStateChange);
MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated); MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);

View file

@ -126,7 +126,7 @@ export default class RoomSubList extends React.PureComponent {
break; break;
case 'view_room': 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.props.list.some((r) => r.roomId === payload.room_id)
) { ) {
this.toggle(); this.toggle();
@ -193,6 +193,7 @@ export default class RoomSubList extends React.PureComponent {
onRoomTileClick = (roomId, ev) => { onRoomTileClick = (roomId, ev) => {
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
show_room_tile: true, // to make sure the room gets scrolled into view
room_id: roomId, room_id: roomId,
clear_search: (ev && (ev.key === Key.ENTER || ev.key === Key.SPACE)), clear_search: (ev && (ev.key === Key.ENTER || ev.key === Key.SPACE)),
}); });

View file

@ -44,7 +44,7 @@ const TagPanel = createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this.unmounted = false; this.unmounted = false;
this.context.on("Group.myMembership", this._onGroupMyMembership); this.context.on("Group.myMembership", this._onGroupMyMembership);
this.context.on("sync", this._onClientSync); this.context.on("sync", this._onClientSync);

View file

@ -35,7 +35,7 @@ export default class UserView extends React.Component {
this.state = {}; this.state = {};
} }
componentWillMount() { componentDidMount() {
if (this.props.userId) { if (this.props.userId) {
this._loadProfileInfo(); this._loadProfileInfo();
} }

View file

@ -69,7 +69,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this.reset = null; this.reset = null;
this._checkServerLiveliness(this.props.serverConfig); this._checkServerLiveliness(this.props.serverConfig);
}, },

View file

@ -113,7 +113,8 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { // TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._unmounted = false; this._unmounted = false;
// map from login step type to a function which will render a control // map from login step type to a function which will render a control

View file

@ -37,7 +37,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
// There is some assymetry between ChangeDisplayName and ChangeAvatar, // There is some assymetry between ChangeDisplayName and ChangeAvatar,
// as ChangeDisplayName will auto-get the name but ChangeAvatar expects // 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). // the URL to be passed to you (because it's also used for room avatars).

View file

@ -120,7 +120,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._unmounted = false; this._unmounted = false;
this._replaceClient(); this._replaceClient();
}, },

View file

@ -54,7 +54,7 @@ export default class SoftLogout extends React.Component {
this.state = { this.state = {
loginView: LOGIN_VIEW.LOADING, 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, busy: false,
password: "", password: "",

View file

@ -60,7 +60,7 @@ export default class CountryDropdown extends React.Component {
}; };
} }
componentWillMount() { componentDidMount() {
if (!this.props.value) { if (!this.props.value) {
// If no value is given, we start with the default // If no value is given, we start with the default
// country selected, but our parent component // country selected, but our parent component

View file

@ -1,7 +1,7 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from "../elements/AccessibleButton";
/* This file contains a collection of components which are used by the /* This file contains a collection of components which are used by the
* InteractiveAuth to prompt the user to enter the information needed * 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. * 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 * setEmailSid: m.login.email.identity only: a function to be called with the
* email sid after a token is requested. * 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): * Each component may also provide the following functions (beyond the standard React ones):
* focus: set the input focus appropriately in the form. * focus: set the input focus appropriately in the form.
*/ */
export const DEFAULT_PHASE = 0;
export const PasswordAuthEntry = createReactClass({ export const PasswordAuthEntry = createReactClass({
displayName: 'PasswordAuthEntry', displayName: 'PasswordAuthEntry',
@ -78,6 +89,11 @@ export const PasswordAuthEntry = createReactClass({
// is the auth logic currently waiting for something to // is the auth logic currently waiting for something to
// happen? // happen?
busy: PropTypes.bool, busy: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired,
},
componentDidMount: function() {
this.props.onPhaseChange(DEFAULT_PHASE);
}, },
getInitialState: function() { getInitialState: function() {
@ -175,6 +191,11 @@ export const RecaptchaAuthEntry = createReactClass({
stageParams: PropTypes.object.isRequired, stageParams: PropTypes.object.isRequired,
errorText: PropTypes.string, errorText: PropTypes.string,
busy: PropTypes.bool, busy: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired,
},
componentDidMount: function() {
this.props.onPhaseChange(DEFAULT_PHASE);
}, },
_onCaptchaResponse: function(response) { _onCaptchaResponse: function(response) {
@ -236,6 +257,11 @@ export const TermsAuthEntry = createReactClass({
errorText: PropTypes.string, errorText: PropTypes.string,
busy: PropTypes.bool, busy: PropTypes.bool,
showContinue: PropTypes.bool, showContinue: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired,
},
componentDidMount: function() {
this.props.onPhaseChange(DEFAULT_PHASE);
}, },
// TODO: [REACT-WARNING] Move this to constructor // TODO: [REACT-WARNING] Move this to constructor
@ -379,6 +405,11 @@ export const EmailIdentityAuthEntry = createReactClass({
stageState: PropTypes.object.isRequired, stageState: PropTypes.object.isRequired,
fail: PropTypes.func.isRequired, fail: PropTypes.func.isRequired,
setEmailSid: PropTypes.func.isRequired, setEmailSid: PropTypes.func.isRequired,
onPhaseChange: PropTypes.func.isRequired,
},
componentDidMount: function() {
this.props.onPhaseChange(DEFAULT_PHASE);
}, },
getInitialState: function() { getInitialState: function() {
@ -421,6 +452,11 @@ export const MsisdnAuthEntry = createReactClass({
clientSecret: PropTypes.func, clientSecret: PropTypes.func,
submitAuthDict: PropTypes.func.isRequired, submitAuthDict: PropTypes.func.isRequired,
matrixClient: PropTypes.object, matrixClient: PropTypes.object,
onPhaseChange: PropTypes.func.isRequired,
},
componentDidMount: function() {
this.props.onPhaseChange(DEFAULT_PHASE);
}, },
getInitialState: function() { getInitialState: function() {
@ -430,7 +466,7 @@ export const MsisdnAuthEntry = createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._submitUrl = null; this._submitUrl = null;
this._sid = null; this._sid = null;
this._msisdn = null; this._msisdn = null;
@ -565,6 +601,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({ export const FallbackAuthEntry = createReactClass({
displayName: 'FallbackAuthEntry', displayName: 'FallbackAuthEntry',
@ -574,6 +695,11 @@ export const FallbackAuthEntry = createReactClass({
loginType: PropTypes.string.isRequired, loginType: PropTypes.string.isRequired,
submitAuthDict: PropTypes.func.isRequired, submitAuthDict: PropTypes.func.isRequired,
errorText: PropTypes.string, errorText: PropTypes.string,
onPhaseChange: PropTypes.func.isRequired,
},
componentDidMount: function() {
this.props.onPhaseChange(DEFAULT_PHASE);
}, },
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
@ -599,7 +725,10 @@ export const FallbackAuthEntry = createReactClass({
} }
}, },
_onShowFallbackClick: function() { _onShowFallbackClick: function(e) {
e.preventDefault();
e.stopPropagation();
const url = this.props.matrixClient.getFallbackAuthUrl( const url = this.props.matrixClient.getFallbackAuthUrl(
this.props.loginType, this.props.loginType,
this.props.authSessionId, this.props.authSessionId,
@ -628,7 +757,7 @@ export const FallbackAuthEntry = createReactClass({
} }
return ( return (
<div> <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} {errorSection}
</div> </div>
); );
@ -641,11 +770,12 @@ const AuthEntryComponents = [
EmailIdentityAuthEntry, EmailIdentityAuthEntry,
MsisdnAuthEntry, MsisdnAuthEntry,
TermsAuthEntry, TermsAuthEntry,
SSOAuthEntry,
]; ];
export default function getEntryComponentForLoginType(loginType) { export default function getEntryComponentForLoginType(loginType) {
for (const c of AuthEntryComponents) { for (const c of AuthEntryComponents) {
if (c.LOGIN_TYPE == loginType) { if (c.LOGIN_TYPE === loginType || c.UNSTABLE_LOGIN_TYPE === loginType) {
return c; return c;
} }
} }

View file

@ -49,7 +49,7 @@ export default class MemberStatusMessageAvatar extends React.Component {
this._button = createRef(); this._button = createRef();
} }
componentWillMount() { componentDidMount() {
if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) { if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) {
throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user"); throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user");
} }

View file

@ -61,7 +61,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions); MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
this._checkPermissions(); this._checkPermissions();
}, },

View file

@ -82,7 +82,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._unmounted = false; this._unmounted = false;
}, },

View file

@ -35,7 +35,7 @@ export default class StatusMessageContextMenu extends React.Component {
}; };
} }
componentWillMount() { componentDidMount() {
const { user } = this.props; const { user } = this.props;
if (!user) { if (!user) {
return; return;

View file

@ -55,7 +55,8 @@ export default createReactClass({
askReason: false, askReason: false,
}), }),
componentWillMount: function() { // TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._reasonField = null; this._reasonField = null;
}, },

View file

@ -279,6 +279,7 @@ export default class DeviceVerifyDialog extends React.Component {
onDone={this._onSasMatchesClick} onDone={this._onSasMatchesClick}
isSelf={MatrixClientPeg.get().getUserId() === this.props.userId} isSelf={MatrixClientPeg.get().getUserId() === this.props.userId}
onStartEmoji={this._onUseSasClick} onStartEmoji={this._onUseSasClick}
inDialog={true}
/>; />;
} }

View file

@ -196,7 +196,8 @@ export default class IncomingSasDialog extends React.Component {
sas={this._showSasEvent.sas} sas={this._showSasEvent.sas}
onCancel={this._onCancelClick} onCancel={this._onCancelClick}
onDone={this._onSasMatchesClick} onDone={this._onSasMatchesClick}
isSelf={this.props.verifier.userId == MatrixClientPeg.get().getUserId()} isSelf={this.props.verifier.userId === MatrixClientPeg.get().getUserId()}
inDialog={true}
/>; />;
} }

View file

@ -1,6 +1,7 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth";
export default createReactClass({ export default createReactClass({
displayName: 'InteractiveAuthDialog', displayName: 'InteractiveAuthDialog',
@ -44,12 +46,36 @@ export default createReactClass({
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
// Optional title and body to show when not showing a particular stage
title: PropTypes.string, 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() { getInitialState: function() {
return { return {
authError: null, authError: null,
// See _onUpdateStagePhase()
uiaStage: null,
uiaStagePhase: null,
}; };
}, },
@ -57,12 +83,21 @@ export default createReactClass({
if (success) { if (success) {
this.props.onFinished(true, result); this.props.onFinished(true, result);
} else { } else {
this.setState({ if (result === ERROR_USER_CANCELLED) {
authError: result, 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() { _onDismissClick: function() {
this.props.onFinished(false); this.props.onFinished(false);
}, },
@ -71,6 +106,23 @@ export default createReactClass({
const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth"); const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); 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; let content;
if (this.state.authError) { if (this.state.authError) {
content = ( content = (
@ -88,11 +140,16 @@ export default createReactClass({
} else { } else {
content = ( content = (
<div id='mx_Dialog_content'> <div id='mx_Dialog_content'>
<InteractiveAuth ref={this._collectInteractiveAuth} {body}
<InteractiveAuth
ref={this._collectInteractiveAuth}
matrixClient={this.props.matrixClient} matrixClient={this.props.matrixClient}
authData={this.props.authData} authData={this.props.authData}
makeRequest={this.props.makeRequest} makeRequest={this.props.makeRequest}
onAuthFinished={this._onAuthFinished} onAuthFinished={this._onAuthFinished}
onStagePhaseChange={this._onUpdateStagePhase}
continueText={continueText}
continueKind={continueKind}
/> />
</div> </div>
); );
@ -101,7 +158,7 @@ export default createReactClass({
return ( return (
<BaseDialog className="mx_InteractiveAuthDialog" <BaseDialog className="mx_InteractiveAuthDialog"
onFinished={this.props.onFinished} onFinished={this.props.onFinished}
title={this.state.authError ? 'Error' : (this.props.title || _t('Authentication'))} title={title}
contentId='mx_Dialog_content' contentId='mx_Dialog_content'
> >
{ content } { content }

View file

@ -36,12 +36,12 @@ export default class RoomSettingsDialog extends React.Component {
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}; };
componentWillMount() { componentDidMount() {
this._dispatcherRef = dis.register(this._onAction); this._dispatcherRef = dis.register(this._onAction);
} }
componentWillUnmount() { componentWillUnmount() {
dis.unregister(this._dispatcherRef); if (this._dispatcherRef) dis.unregister(this._dispatcherRef);
} }
_onAction = (payload) => { _onAction = (payload) => {

View file

@ -30,7 +30,7 @@ export default createReactClass({
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}, },
componentWillMount: async function() { componentDidMount: async function() {
const recommended = await this.props.room.getRecommendedVersion(); const recommended = await this.props.room.getRecommendedVersion();
this._targetVersion = recommended.version; this._targetVersion = recommended.version;
this.setState({busy: false}); this.setState({busy: false});

View file

@ -75,8 +75,8 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
console.info('SetPasswordDialog component will mount'); console.info('SetPasswordDialog component did mount');
}, },
_onPasswordChanged: function(res) { _onPasswordChanged: function(res) {

View file

@ -121,7 +121,7 @@ export default class ShareDialog extends React.Component {
}); });
} }
componentWillMount() { componentDidMount() {
if (this.props.target instanceof Room) { if (this.props.target instanceof Room) {
const permalinkCreator = new RoomPermalinkCreator(this.props.target); const permalinkCreator = new RoomPermalinkCreator(this.props.target);
permalinkCreator.load(); permalinkCreator.load();

View file

@ -87,7 +87,7 @@ export default createReactClass({
onSend: PropTypes.func.isRequired, onSend: PropTypes.func.isRequired,
}, },
componentWillMount: function() { componentDidMount: function() {
MatrixClientPeg.get().on("deviceVerificationChanged", this._onDeviceVerificationChanged); MatrixClientPeg.get().on("deviceVerificationChanged", this._onDeviceVerificationChanged);
}, },

View file

@ -48,6 +48,7 @@ export default class VerificationRequestDialog extends React.Component {
verificationRequestPromise={this.props.verificationRequestPromise} verificationRequestPromise={this.props.verificationRequestPromise}
onClose={this.props.onFinished} onClose={this.props.onFinished}
member={member} member={member}
inDialog={true}
/> />
</BaseDialog>; </BaseDialog>;
} }

View file

@ -2,6 +2,7 @@
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
const ENABLE_REACT_PERF = false; 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 { export default class AppTile extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
// The key used for PersistedElement // The key used for PersistedElement
this._persistKey = 'widget_' + this.props.id; this._persistKey = 'widget_' + this.props.app.id;
this.state = this._getNewState(props); 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 // This is a function to make the impact of calling SettingsStore slightly less
const hasPermissionToLoad = () => { const hasPermissionToLoad = () => {
const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", newProps.room.roomId); const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", newProps.room.roomId);
return !!currentlyAllowedWidgets[newProps.eventId]; return !!currentlyAllowedWidgets[newProps.app.eventId];
}; };
const PersistedElement = sdk.getComponent("elements.PersistedElement"); 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 initialising: true, // True while we are mangling the widget URL
// True while the iframe content is loading // True while the iframe content is loading
loading: this.props.waitForIframeLoad && !PersistedElement.isMounted(this._persistKey), 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 // 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 // added it to the room, or if explicitly granted by the user
hasPermissionToLoad: newProps.userId === newProps.creatorUserId || hasPermissionToLoad(), hasPermissionToLoad: newProps.userId === newProps.creatorUserId || hasPermissionToLoad(),
@ -103,7 +122,7 @@ export default class AppTile extends React.Component {
* @return {Boolean} True if capability supported * @return {Boolean} True if capability supported
*/ */
_hasCapability(capability) { _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); const params = qs.parse(u.query);
// Append widget ID to query parameters // 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 // Append current / parent URL, minus the hash because that will change when
// we view a different room (ie. may change for persistent widgets) // we view a different room (ie. may change for persistent widgets)
params.parentUrl = window.location.href.split('#', 2)[0]; params.parentUrl = window.location.href.split('#', 2)[0];
@ -137,35 +156,33 @@ export default class AppTile extends React.Component {
isMixedContent() { isMixedContent() {
const parentContentProtocol = window.location.protocol; const parentContentProtocol = window.location.protocol;
const u = url.parse(this.props.url); const u = url.parse(this.props.app.url);
const childContentProtocol = u.protocol; const childContentProtocol = u.protocol;
if (parentContentProtocol === 'https:' && childContentProtocol !== 'https:') { if (parentContentProtocol === 'https:' && childContentProtocol !== 'https:') {
console.warn("Refusing to load mixed-content app:", 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 true;
} }
return false; return false;
} }
componentWillMount() { componentDidMount() {
// Only fetch IM token on mount if we're showing and have permission to load // Only fetch IM token on mount if we're showing and have permission to load
if (this.props.show && this.state.hasPermissionToLoad) { if (this.props.show && this.state.hasPermissionToLoad) {
this.setScalarToken(); this.setScalarToken();
} }
}
componentDidMount() {
// Widget action listeners // Widget action listeners
this.dispatcherRef = dis.register(this._onAction); this.dispatcherRef = dis.register(this._onAction);
} }
componentWillUnmount() { componentWillUnmount() {
// Widget action listeners // 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 it's not remaining on screen, get rid of the PersistedElement container
if (!ActiveWidgetStore.getWidgetPersistence(this.props.id)) { if (!ActiveWidgetStore.getWidgetPersistence(this.props.app.id)) {
ActiveWidgetStore.destroyPersistentWidget(this.props.id); ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
const PersistedElement = sdk.getComponent("elements.PersistedElement"); const PersistedElement = sdk.getComponent("elements.PersistedElement");
PersistedElement.destroyElement(this._persistKey); 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 * Component initialisation is only complete when this function has resolved
*/ */
setScalarToken() { setScalarToken() {
if (!WidgetUtils.isScalarUrl(this.props.url)) { if (!WidgetUtils.isScalarUrl(this.props.app.url)) {
console.warn('Non-scalar widget, not setting scalar token!', url); console.warn('Non-scalar widget, not setting scalar token!', url);
this.setState({ this.setState({
error: null, error: null,
widgetUrl: this._addWurlParams(this.props.url), widgetUrl: this._addWurlParams(this.props.app.url),
initialising: false, initialising: false,
}); });
return; return;
@ -191,7 +208,7 @@ export default class AppTile extends React.Component {
console.warn("No integration manager - not setting scalar token", url); console.warn("No integration manager - not setting scalar token", url);
this.setState({ this.setState({
error: null, error: null,
widgetUrl: this._addWurlParams(this.props.url), widgetUrl: this._addWurlParams(this.props.app.url),
initialising: false, initialising: false,
}); });
return; return;
@ -204,7 +221,7 @@ export default class AppTile extends React.Component {
console.warn('Non-scalar manager, not setting scalar token!', url); console.warn('Non-scalar manager, not setting scalar token!', url);
this.setState({ this.setState({
error: null, error: null,
widgetUrl: this._addWurlParams(this.props.url), widgetUrl: this._addWurlParams(this.props.app.url),
initialising: false, initialising: false,
}); });
return; return;
@ -217,7 +234,7 @@ export default class AppTile extends React.Component {
this._scalarClient.getScalarToken().then((token) => { this._scalarClient.getScalarToken().then((token) => {
// Append scalar_token as a query param if not already present // Append scalar_token as a query param if not already present
this._scalarClient.scalarToken = token; 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); const params = qs.parse(u.query);
if (!params.scalar_token) { if (!params.scalar_token) {
params.scalar_token = encodeURIComponent(token); params.scalar_token = encodeURIComponent(token);
@ -246,7 +263,7 @@ export default class AppTile extends React.Component {
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.url !== this.props.url) { if (nextProps.app.url !== this.props.app.url) {
this._getNewState(nextProps); this._getNewState(nextProps);
// Fetch IM token for new URL if we're showing and have permission to load // Fetch IM token for new URL if we're showing and have permission to load
if (this.props.show && this.state.hasPermissionToLoad) { if (this.props.show && this.state.hasPermissionToLoad) {
@ -280,7 +297,7 @@ export default class AppTile extends React.Component {
} }
_onEditClick() { _onEditClick() {
console.log("Edit widget ID ", this.props.id); console.log("Edit widget ID ", this.props.app.id);
if (this.props.onEditClick) { if (this.props.onEditClick) {
this.props.onEditClick(); this.props.onEditClick();
} else { } else {
@ -289,13 +306,13 @@ export default class AppTile extends React.Component {
IntegrationManagers.sharedInstance().openAll( IntegrationManagers.sharedInstance().openAll(
this.props.room, this.props.room,
'type_' + this.props.type, 'type_' + this.props.type,
this.props.id, this.props.app.id,
); );
} else { } else {
IntegrationManagers.sharedInstance().getPrimaryManager().open( IntegrationManagers.sharedInstance().getPrimaryManager().open(
this.props.room, this.props.room,
'type_' + this.props.type, 'type_' + this.props.type,
this.props.id, this.props.app.id,
); );
} }
} }
@ -303,7 +320,7 @@ export default class AppTile extends React.Component {
_onSnapshotClick() { _onSnapshotClick() {
console.warn("Requesting widget snapshot"); console.warn("Requesting widget snapshot");
ActiveWidgetStore.getWidgetMessaging(this.props.id).getScreenshot() ActiveWidgetStore.getWidgetMessaging(this.props.app.id).getScreenshot()
.catch((err) => { .catch((err) => {
console.error("Failed to get screenshot", err); console.error("Failed to get screenshot", err);
}) })
@ -351,7 +368,7 @@ export default class AppTile extends React.Component {
WidgetUtils.setRoomWidget( WidgetUtils.setRoomWidget(
this.props.room.roomId, this.props.room.roomId,
this.props.id, this.props.app.id,
).catch((e) => { ).catch((e) => {
console.error('Failed to delete widget', e); console.error('Failed to delete widget', e);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -369,7 +386,7 @@ export default class AppTile extends React.Component {
} }
_onRevokeClicked() { _onRevokeClicked() {
console.info("Revoke widget permissions - %s", this.props.id); console.info("Revoke widget permissions - %s", this.props.app.id);
this._revokeWidgetPermission(); this._revokeWidgetPermission();
} }
@ -380,10 +397,10 @@ export default class AppTile extends React.Component {
// Destroy the old widget messaging before starting it back up again. Some widgets // 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 // have startup routines that run when they are loaded, so we just need to reinitialize
// the messaging for them. // the messaging for them.
ActiveWidgetStore.delWidgetMessaging(this.props.id); ActiveWidgetStore.delWidgetMessaging(this.props.app.id);
this._setupWidgetMessaging(); 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}); this.setState({loading: false});
} }
@ -391,10 +408,10 @@ export default class AppTile extends React.Component {
// FIXME: There's probably no reason to do this here: it should probably be done entirely // FIXME: There's probably no reason to do this here: it should probably be done entirely
// in ActiveWidgetStore. // in ActiveWidgetStore.
const widgetMessaging = new WidgetMessaging( const widgetMessaging = new WidgetMessaging(
this.props.id, this.props.url, this.props.userWidget, this._appFrame.current.contentWindow); this.props.app.id, this._getRenderedUrl(), this.props.userWidget, this._appFrame.current.contentWindow);
ActiveWidgetStore.setWidgetMessaging(this.props.id, widgetMessaging); ActiveWidgetStore.setWidgetMessaging(this.props.app.id, widgetMessaging);
widgetMessaging.getCapabilities().then((requestedCapabilities) => { 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 || []; requestedCapabilities = requestedCapabilities || [];
// Allow whitelisted capabilities // Allow whitelisted capabilities
@ -406,7 +423,7 @@ export default class AppTile extends React.Component {
}, this.props.whitelistCapabilities); }, this.props.whitelistCapabilities);
if (requestedWhitelistCapabilies.length > 0 ) { if (requestedWhitelistCapabilies.length > 0 ) {
console.warn(`Widget ${this.props.id} allowing requested, whitelisted properties: ` + console.warn(`Widget ${this.props.app.id} allowing requested, whitelisted properties: ` +
requestedWhitelistCapabilies, requestedWhitelistCapabilies,
); );
} }
@ -414,7 +431,7 @@ export default class AppTile extends React.Component {
// TODO -- Add UI to warn about and optionally allow requested capabilities // 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) { if (this.props.onCapabilityRequest) {
this.props.onCapabilityRequest(requestedCapabilities); this.props.onCapabilityRequest(requestedCapabilities);
@ -422,16 +439,16 @@ export default class AppTile extends React.Component {
// We only tell Jitsi widgets that we're ready because they're realistically the only ones // We only tell Jitsi widgets that we're ready because they're realistically the only ones
// using this custom extension to the widget API. // using this custom extension to the widget API.
if (this.props.type === 'jitsi') { if (this.props.app.type === 'jitsi') {
widgetMessaging.flagReadyToContinue(); widgetMessaging.flagReadyToContinue();
} }
}).catch((err) => { }).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) { _onAction(payload) {
if (payload.widgetId === this.props.id) { if (payload.widgetId === this.props.app.id) {
switch (payload.action) { switch (payload.action) {
case 'm.sticker': case 'm.sticker':
if (this._hasCapability('m.sticker')) { if (this._hasCapability('m.sticker')) {
@ -460,9 +477,9 @@ export default class AppTile extends React.Component {
_grantWidgetPermission() { _grantWidgetPermission() {
const roomId = this.props.room.roomId; 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); 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(() => { SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).then(() => {
this.setState({hasPermissionToLoad: true}); this.setState({hasPermissionToLoad: true});
@ -476,14 +493,14 @@ export default class AppTile extends React.Component {
_revokeWidgetPermission() { _revokeWidgetPermission() {
const roomId = this.props.room.roomId; 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); 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(() => { SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).then(() => {
this.setState({hasPermissionToLoad: false}); this.setState({hasPermissionToLoad: false});
// Force the widget to be non-persistent (able to be deleted/forgotten) // 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"); const PersistedElement = sdk.getComponent("elements.PersistedElement");
PersistedElement.destroyElement(this._persistKey); PersistedElement.destroyElement(this._persistKey);
}).catch(err => { }).catch(err => {
@ -494,8 +511,8 @@ export default class AppTile extends React.Component {
formatAppTileName() { formatAppTileName() {
let appTileName = "No name"; let appTileName = "No name";
if (this.props.name && this.props.name.trim()) { if (this.props.app.name && this.props.app.name.trim()) {
appTileName = this.props.name.trim(); appTileName = this.props.app.name.trim();
} }
return appTileName; return appTileName;
} }
@ -519,20 +536,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) { if (ENABLE_REACT_PERF) {
parsedWidgetUrl.search = null; parsedWidgetUrl.search = null;
parsedWidgetUrl.query.react_perf = true; parsedWidgetUrl.query.react_perf = true;
} }
let safeWidgetUrl = ''; let safeWidgetUrl = '';
if (ALLOWED_APP_URL_SCHEMES.includes(parsedWidgetUrl.protocol) || ( 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'
)) {
safeWidgetUrl = url.format(parsedWidgetUrl); safeWidgetUrl = url.format(parsedWidgetUrl);
} }
return safeWidgetUrl; return safeWidgetUrl;
@ -562,9 +637,9 @@ export default class AppTile extends React.Component {
_onPopoutWidgetClick() { _onPopoutWidgetClick() {
// Using Object.assign workaround as the following opens in a new window instead of a new tab. // 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'), Object.assign(document.createElement('a'),
{ target: '_blank', href: this._getSafeUrl(), rel: 'noreferrer noopener'}).click(); { target: '_blank', href: this._getPopoutUrl(), rel: 'noreferrer noopener'}).click();
} }
_onReloadWidgetClick() { _onReloadWidgetClick() {
@ -641,7 +716,7 @@ export default class AppTile extends React.Component {
<iframe <iframe
allow={iframeFeatures} allow={iframeFeatures}
ref={this._appFrame} ref={this._appFrame}
src={this._getSafeUrl()} src={this._getRenderedUrl()}
allowFullScreen={true} allowFullScreen={true}
sandbox={sandboxFlags} sandbox={sandboxFlags}
onLoad={this._onLoaded} /> onLoad={this._onLoaded} />
@ -706,7 +781,7 @@ export default class AppTile extends React.Component {
} }
return <React.Fragment> return <React.Fragment>
<div className={appTileClass} id={this.props.id}> <div className={appTileClass} id={this.props.app.id}>
{ this.props.showMenubar && { this.props.showMenubar &&
<div ref={this._menu_bar} className={menuBarClasses} onClick={this.onClickMenuBar}> <div ref={this._menu_bar} className={menuBarClasses} onClick={this.onClickMenuBar}>
<span className="mx_AppTileMenuBarTitle" style={{pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false)}}> <span className="mx_AppTileMenuBarTitle" style={{pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false)}}>
@ -753,12 +828,8 @@ export default class AppTile extends React.Component {
AppTile.displayName = 'AppTile'; AppTile.displayName = 'AppTile';
AppTile.propTypes = { AppTile.propTypes = {
id: PropTypes.string.isRequired, app: PropTypes.object.isRequired,
eventId: PropTypes.string, // required for room widgets
url: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
room: 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. // 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. // This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
fullWidth: PropTypes.bool, fullWidth: PropTypes.bool,
@ -805,7 +876,6 @@ AppTile.propTypes = {
}; };
AppTile.defaultProps = { AppTile.defaultProps = {
url: "",
waitForIframeLoad: true, waitForIframeLoad: true,
showMenubar: true, showMenubar: true,
showTitle: true, showTitle: true,

View file

@ -38,7 +38,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged); cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
}, },

View file

@ -42,7 +42,7 @@ export default class EditableTextContainer extends React.Component {
this._onValueChanged = this._onValueChanged.bind(this); this._onValueChanged = this._onValueChanged.bind(this);
} }
componentWillMount() { componentDidMount() {
if (this.props.getInitialValue === undefined) { if (this.props.getInitialValue === undefined) {
// use whatever was given in the initialValue property. // use whatever was given in the initialValue property.
return; return;

View file

@ -24,8 +24,8 @@ import SettingsStore from "../../../settings/SettingsStore";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
function languageMatchesSearchQuery(query, language) { function languageMatchesSearchQuery(query, language) {
if (language.label.toUpperCase().indexOf(query.toUpperCase()) == 0) return true; if (language.label.toUpperCase().includes(query.toUpperCase())) return true;
if (language.value.toUpperCase() == query.toUpperCase()) return true; if (language.value.toUpperCase() === query.toUpperCase()) return true;
return false; return false;
} }
@ -40,7 +40,7 @@ export default class LanguageDropdown extends React.Component {
}; };
} }
componentWillMount() { componentDidMount() {
languageHandler.getAllLanguagesFromJson().then((langs) => { languageHandler.getAllLanguagesFromJson().then((langs) => {
langs.sort(function(a, b) { langs.sort(function(a, b) {
if (a.label < b.label) return -1; if (a.label < b.label) return -1;

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2018 New Vector Ltd 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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); this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
ActiveWidgetStore.on('update', this._onActiveWidgetStoreUpdate); ActiveWidgetStore.on('update', this._onActiveWidgetStoreUpdate);
}, },
@ -75,11 +75,7 @@ export default createReactClass({
const AppTile = sdk.getComponent('elements.AppTile'); const AppTile = sdk.getComponent('elements.AppTile');
return <AppTile return <AppTile
key={app.id} key={app.id}
id={app.id} app={app}
eventId={app.eventId}
url={app.url}
name={app.name}
type={app.type}
fullWidth={true} fullWidth={true}
room={persistentWidgetInRoom} room={persistentWidgetInRoom}
userId={MatrixClientPeg.get().credentials.userId} userId={MatrixClientPeg.get().credentials.userId}

View file

@ -155,10 +155,10 @@ const Pill = createReactClass({
this.setState({resourceId, pillType, member, group, room}); this.setState({resourceId, pillType, member, group, room});
}, },
componentWillMount() { componentDidMount() {
this._unmounted = false; this._unmounted = false;
this._matrixClient = MatrixClientPeg.get(); this._matrixClient = MatrixClientPeg.get();
this.componentWillReceiveProps(this.props); this.componentWillReceiveProps(this.props); // HACK: We shouldn't be calling lifecycle functions ourselves.
}, },
componentWillUnmount() { componentWillUnmount() {

View file

@ -62,7 +62,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
// TODO: [REACT-WARNING] Move this to class constructor // TODO: [REACT-WARNING] Move this to class constructor
this._initStateFromProps(this.props); this._initStateFromProps(this.props);
}, },

View file

@ -184,7 +184,7 @@ export default class ReplyThread extends React.Component {
ref={ref} permalinkCreator={permalinkCreator} />; ref={ref} permalinkCreator={permalinkCreator} />;
} }
componentWillMount() { componentDidMount() {
this.unmounted = false; this.unmounted = false;
this.room = this.context.getRoom(this.props.parentEv.getRoomId()); this.room = this.context.getRoom(this.props.parentEv.getRoomId());
this.room.on("Room.redaction", this.onRoomRedaction); this.room.on("Room.redaction", this.onRoomRedaction);

View file

@ -36,11 +36,9 @@ const TintableSvg = createReactClass({
idSequence: 0, idSequence: 0,
}, },
componentWillMount: function() {
this.fixups = [];
},
componentDidMount: function() { componentDidMount: function() {
this.fixups = [];
this.id = TintableSvg.idSequence++; this.id = TintableSvg.idSequence++;
TintableSvg.mounts[this.id] = this; TintableSvg.mounts[this.id] = this;
}, },

View file

@ -49,7 +49,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._unmounted = false; this._unmounted = false;
this._initGroupStore(this.props.groupId); this._initGroupStore(this.props.groupId);
}, },

View file

@ -47,7 +47,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._unmounted = false; this._unmounted = false;
this._initGroupStore(this.props.groupId); this._initGroupStore(this.props.groupId);
}, },

View file

@ -36,7 +36,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._initGroupStore(this.props.groupId); this._initGroupStore(this.props.groupId);
}, },

View file

@ -47,7 +47,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._initGroupStore(this.props.groupId); this._initGroupStore(this.props.groupId);
}, },

View file

@ -39,7 +39,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._unmounted = false; this._unmounted = false;
this._initGroupStore(this.props.groupId); this._initGroupStore(this.props.groupId);
}, },

View file

@ -55,7 +55,7 @@ const GroupTile = createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => { FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => {
this.setState({profile}); this.setState({profile});
}).catch((err) => { }).catch((err) => {

View file

@ -34,7 +34,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this.context.getJoinedGroups().then((result) => { this.context.getJoinedGroups().then((result) => {
this.setState({groups: result.groups || [], error: null}); this.setState({groups: result.groups || [], error: null});
}, (err) => { }, (err) => {

View file

@ -67,11 +67,6 @@ export default class MImageBody extends React.Component {
this._image = createRef(); 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! // FIXME: factor this out and aplpy it to MVideoBody and MAudioBody too!
onClientSync(syncState, prevState) { onClientSync(syncState, prevState) {
if (this.unmounted) return; if (this.unmounted) return;
@ -258,6 +253,9 @@ export default class MImageBody extends React.Component {
} }
componentDidMount() { componentDidMount() {
this.unmounted = false;
this.context.on('sync', this.onClientSync);
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) { if (content.file !== undefined && this.state.decryptedUrl === null) {
let thumbnailPromise = Promise.resolve(null); let thumbnailPromise = Promise.resolve(null);

View file

@ -42,7 +42,7 @@ export default createReactClass({
}; };
}, },
componentWillMount() { componentDidMount() {
this.unmounted = false; this.unmounted = false;
this._updateRelatedGroups(); this._updateRelatedGroups();

View file

@ -31,7 +31,7 @@ import {_t} from "../../../languageHandler";
const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"]; const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"];
const EncryptionPanel = (props) => { const EncryptionPanel = (props) => {
const {verificationRequest, verificationRequestPromise, member, onClose, layout, isRoomEncrypted} = props; const {verificationRequest, verificationRequestPromise, member, onClose, layout, isRoomEncrypted, inDialog} = props;
const [request, setRequest] = useState(verificationRequest); const [request, setRequest] = useState(verificationRequest);
// state to show a spinner immediately after clicking "start verification", // state to show a spinner immediately after clicking "start verification",
// before we have a request // before we have a request
@ -133,6 +133,7 @@ const EncryptionPanel = (props) => {
member={member} member={member}
request={request} request={request}
key={request.channel.transactionId} key={request.channel.transactionId}
inDialog={inDialog}
phase={phase} /> phase={phase} />
</React.Fragment>); </React.Fragment>);
} }
@ -142,6 +143,7 @@ EncryptionPanel.propTypes = {
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
verificationRequest: PropTypes.object, verificationRequest: PropTypes.object,
layout: PropTypes.string, layout: PropTypes.string,
inDialog: PropTypes.bool,
}; };
export default EncryptionPanel; export default EncryptionPanel;

View file

@ -40,7 +40,7 @@ export default class HeaderButtons extends React.Component {
}; };
} }
componentWillMount() { componentDidMount() {
this._storeToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelUpdate.bind(this)); this._storeToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelUpdate.bind(this));
this._dispatcherRef = dis.register(this.onAction.bind(this)); // used by subclasses this._dispatcherRef = dis.register(this.onAction.bind(this)); // used by subclasses
} }

View file

@ -245,6 +245,7 @@ export default class VerificationPanel extends React.PureComponent {
sas={this.state.sasEvent.sas} sas={this.state.sasEvent.sas}
onCancel={this._onSasMismatchesClick} onCancel={this._onSasMismatchesClick}
onDone={this._onSasMatchesClick} onDone={this._onSasMatchesClick}
inDialog={this.props.inDialog}
/> />
</div>; </div>;
} else { } else {

View file

@ -55,13 +55,10 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
ScalarMessaging.startListening(); ScalarMessaging.startListening();
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
WidgetEchoStore.on('update', this._updateApps); WidgetEchoStore.on('update', this._updateApps);
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
}, },
@ -71,7 +68,7 @@ export default createReactClass({
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents); MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
} }
WidgetEchoStore.removeListener('update', this._updateApps); WidgetEchoStore.removeListener('update', this._updateApps);
dis.unregister(this.dispatcherRef); if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
}, },
componentWillReceiveProps(newProps) { componentWillReceiveProps(newProps) {
@ -160,11 +157,7 @@ export default createReactClass({
return (<AppTile return (<AppTile
key={app.id} key={app.id}
id={app.id} app={app}
eventId={app.eventId}
url={app.url}
name={app.name}
type={app.type}
fullWidth={arr.length<2 ? true : false} fullWidth={arr.length<2 ? true : false}
room={this.props.room} room={this.props.room}
userId={this.props.userId} userId={this.props.userId}

View file

@ -223,7 +223,7 @@ export default class EditMessageComposer extends React.Component {
this.props.editState.setEditorState(caret, parts); this.props.editState.setEditorState(caret, parts);
} }
componentWillMount() { componentDidMount() {
this._createEditorModel(); this._createEditorModel();
} }

View file

@ -30,14 +30,12 @@ export default createReactClass({
onCancelClick: PropTypes.func.isRequired, onCancelClick: PropTypes.func.isRequired,
}, },
componentWillMount: function() { componentDidMount: function() {
dis.dispatch({ dis.dispatch({
action: 'panel_disable', action: 'panel_disable',
middleDisabled: true, middleDisabled: true,
}); });
},
componentDidMount: function() {
document.addEventListener('keydown', this._onKeyDown); document.addEventListener('keydown', this._onKeyDown);
}, },

View file

@ -79,7 +79,8 @@ export default createReactClass({
contextType: MatrixClientContext, contextType: MatrixClientContext,
}, },
componentWillMount: function() { // TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._cancelDeviceList = null; this._cancelDeviceList = null;
const cli = this.context; const cli = this.context;
@ -98,9 +99,7 @@ export default createReactClass({
cli.on("accountData", this.onAccountData); cli.on("accountData", this.onAccountData);
this._checkIgnoreState(); this._checkIgnoreState();
},
componentDidMount: function() {
this._updateStateForNewMember(this.props.member); this._updateStateForNewMember(this.props.member);
}, },

View file

@ -49,7 +49,8 @@ export default createReactClass({
} }
}, },
componentWillMount: function() { // TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._mounted = true; this._mounted = true;
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) { if (cli.hasLazyLoadMembersEnabled()) {

View file

@ -49,7 +49,7 @@ export default class RoomBreadcrumbs extends React.Component {
this._scroller = createRef(); this._scroller = createRef();
} }
componentWillMount() { componentDidMount() {
this._dispatcherRef = dis.register(this.onAction); this._dispatcherRef = dis.register(this.onAction);
const storedRooms = SettingsStore.getValue("breadcrumb_rooms"); const storedRooms = SettingsStore.getValue("breadcrumb_rooms");

View file

@ -290,6 +290,7 @@ export default createReactClass({
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
room_id: room.roomId, room_id: room.roomId,
show_room_tile: true, // to make sure the room gets scrolled into view
}); });
} }
break; break;

View file

@ -34,7 +34,8 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { // TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
const room = this.props.room; const room = this.props.room;
const name = room.currentState.getStateEvents('m.room.name', ''); const name = room.currentState.getStateEvents('m.room.name', '');
const myId = MatrixClientPeg.get().credentials.userId; const myId = MatrixClientPeg.get().credentials.userId;

View file

@ -97,7 +97,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._checkInvitedEmail(); this._checkInvitedEmail();
}, },

View file

@ -44,7 +44,7 @@ export default class RoomRecoveryReminder extends React.PureComponent {
}; };
} }
componentWillMount() { componentDidMount() {
this._loadBackupStatus(); this._loadBackupStatus();
} }
@ -61,7 +61,6 @@ export default class RoomRecoveryReminder extends React.PureComponent {
loading: false, loading: false,
error: e, error: e,
}); });
return;
} }
} }

View file

@ -203,7 +203,7 @@ export default createReactClass({
case 'view_room': case 'view_room':
// when the room is selected make sure its tile is visible, for breadcrumbs/keyboard shortcut access // when the room is selected make sure its tile is visible, for breadcrumbs/keyboard shortcut access
if (payload.room_id === this.props.room.roomId) { if (payload.room_id === this.props.room.roomId && payload.show_room_tile) {
this._scrollIntoView(); this._scrollIntoView();
} }
break; break;

View file

@ -33,7 +33,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
const room = this.props.room; const room = this.props.room;
const topic = room.currentState.getStateEvents('m.room.topic', ''); const topic = room.currentState.getStateEvents('m.room.topic', '');
this.setState({ this.setState({

View file

@ -31,7 +31,7 @@ export default createReactClass({
recommendation: PropTypes.object.isRequired, recommendation: PropTypes.object.isRequired,
}, },
componentWillMount: function() { componentDidMount: function() {
const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", ""); const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", "");
this.setState({upgraded: tombstone && tombstone.getContent().replacement_room}); this.setState({upgraded: tombstone && tombstone.getContent().replacement_room});

View file

@ -240,6 +240,14 @@ export default class Stickerpicker extends React.Component {
// Set default name // Set default name
stickerpickerWidget.content.name = stickerpickerWidget.name || _t("Stickerpack"); stickerpickerWidget.content.name = stickerpickerWidget.name || _t("Stickerpack");
// FIXME: could this use the same code as other apps?
const stickerApp = {
id: stickerpickerWidget.id,
url: stickerpickerWidget.content.url,
name: stickerpickerWidget.content.name,
type: stickerpickerWidget.content.type,
};
stickersContent = ( stickersContent = (
<div className='mx_Stickers_content_container'> <div className='mx_Stickers_content_container'>
<div <div
@ -253,11 +261,8 @@ export default class Stickerpicker extends React.Component {
> >
<PersistedElement persistKey={PERSISTED_ELEMENT_KEY} style={{zIndex: STICKERPICKER_Z_INDEX}}> <PersistedElement persistKey={PERSISTED_ELEMENT_KEY} style={{zIndex: STICKERPICKER_Z_INDEX}}>
<AppTile <AppTile
id={stickerpickerWidget.id} app={stickerApp}
url={stickerpickerWidget.content.url}
name={stickerpickerWidget.content.name}
room={this.props.room} room={this.props.room}
type={stickerpickerWidget.content.type}
fullWidth={true} fullWidth={true}
userId={MatrixClientPeg.get().credentials.userId} userId={MatrixClientPeg.get().credentials.userId}
creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId} creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId}

View file

@ -51,7 +51,7 @@ export default class ThirdPartyMemberInfo extends React.Component {
}; };
} }
componentWillMount(): void { componentDidMount(): void {
MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents); MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
} }

View file

@ -54,7 +54,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping); MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
}, },

View file

@ -55,7 +55,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents); MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
}, },

View file

@ -78,7 +78,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._sessionStore = sessionStore; this._sessionStore = sessionStore;
this._sessionStoreToken = this._sessionStore.addListener( this._sessionStoreToken = this._sessionStore.addListener(
this._setStateFromSessionStore, this._setStateFromSessionStore,

View file

@ -23,6 +23,7 @@ import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import {SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents";
export default class DevicesPanel extends React.Component { export default class DevicesPanel extends React.Component {
constructor(props) { constructor(props) {
@ -123,11 +124,29 @@ export default class DevicesPanel extends React.Component {
// pop up an interactive auth dialog // pop up an interactive auth dialog
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
const dialogAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("Use Single Sign On to continue"),
body: _t("Confirm deleting these sessions by using Single Sign On to prove your identity."),
continueText: _t("Single Sign On"),
continueKind: "primary",
},
[SSOAuthEntry.PHASE_POSTAUTH]: {
title: _t("Confirm deleting these sessions"),
body: _t("Click the button below to confirm deleting these sessions."),
continueText: _t("Delete sessions"),
continueKind: "danger",
},
};
Modal.createTrackedDialog('Delete Device Dialog', '', InteractiveAuthDialog, { Modal.createTrackedDialog('Delete Device Dialog', '', InteractiveAuthDialog, {
title: _t("Authentication"), title: _t("Authentication"),
matrixClient: MatrixClientPeg.get(), matrixClient: MatrixClientPeg.get(),
authData: error.data, authData: error.data,
makeRequest: this._makeDeleteRequest.bind(this), makeRequest: this._makeDeleteRequest.bind(this),
aestheticsForStagePhases: {
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
},
}); });
}).catch((e) => { }).catch((e) => {
console.error("Error deleting sessions", e); console.error("Error deleting sessions", e);

View file

@ -63,7 +63,7 @@ export default class EventIndexPanel extends React.Component {
} }
} }
async componentWillMount(): void { async componentDidMount(): void {
this.updateState(); this.updateState();
} }

View file

@ -38,7 +38,7 @@ export default class KeyBackupPanel extends React.PureComponent {
}; };
} }
componentWillMount() { componentDidMount() {
this._checkKeyBackupStatus(); this._checkKeyBackupStatus();
MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatus); MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatus);

View file

@ -87,7 +87,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._refreshFromServer(); this._refreshFromServer();
}, },

View file

@ -40,7 +40,7 @@ export default class HelpUserSettingsTab extends React.Component {
}; };
} }
componentWillMount(): void { componentDidMount(): void {
PlatformPeg.get().getAppVersion().then((ver) => this.setState({vectorVersion: ver})).catch((e) => { PlatformPeg.get().getAppVersion().then((ver) => this.setState({vectorVersion: ver})).catch((e) => {
console.error("Error getting vector version: ", e); console.error("Error getting vector version: ", e);
}); });

View file

@ -81,7 +81,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
}; };
} }
async componentWillMount(): void { async componentDidMount(): void {
const platform = PlatformPeg.get(); const platform = PlatformPeg.get();
const autoLaunchSupported = await platform.supportsAutoLaunch(); const autoLaunchSupported = await platform.supportsAutoLaunch();

View file

@ -33,6 +33,7 @@ export default class VerificationShowSas extends React.Component {
onCancel: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired,
sas: PropTypes.object.isRequired, sas: PropTypes.object.isRequired,
isSelf: PropTypes.bool, isSelf: PropTypes.bool,
inDialog: PropTypes.bool, // whether this component is being shown in a dialog and to use DialogButtons
}; };
constructor(props) { constructor(props) {
@ -112,7 +113,7 @@ export default class VerificationShowSas extends React.Component {
text = _t("Cancelling…"); text = _t("Cancelling…");
} }
confirm = <PendingActionSpinner text={text} />; confirm = <PendingActionSpinner text={text} />;
} else { } else if (this.props.inDialog) {
// FIXME: stop using DialogButtons here once this component is only used in the right panel verification // FIXME: stop using DialogButtons here once this component is only used in the right panel verification
confirm = <DialogButtons confirm = <DialogButtons
primaryButton={_t("They match")} primaryButton={_t("They match")}
@ -122,6 +123,15 @@ export default class VerificationShowSas extends React.Component {
onCancel={this.onDontMatchClick} onCancel={this.onDontMatchClick}
cancelButtonClass="mx_UserInfo_wideButton" cancelButtonClass="mx_UserInfo_wideButton"
/>; />;
} else {
confirm = <React.Fragment>
<AccessibleButton onClick={this.onMatchClick} kind="primary">
{ _t("They match") }
</AccessibleButton>
<AccessibleButton onClick={this.onDontMatchClick} kind="danger">
{ _t("They don't match") }
</AccessibleButton>
</React.Fragment>;
} }
return <div className="mx_VerificationShowSas"> return <div className="mx_VerificationShowSas">

View file

@ -40,7 +40,7 @@ export default createReactClass({
}; };
}, },
componentWillMount: function() { componentDidMount: function() {
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this.dispatcherRef = dis.register(this._onAction); this.dispatcherRef = dis.register(this._onAction);
}, },

View file

@ -1,8 +1,17 @@
{ {
"This email address is already in use": "This email address is already in use", "This email address is already in use": "This email address is already in use",
"This phone number is already in use": "This phone number is already in use", "This phone number is already in use": "This phone number is already in use",
"Use Single Sign On to continue": "Use Single Sign On to continue",
"Confirm adding this email address by using Single Sign On to prove your identity.": "Confirm adding this email address by using Single Sign On to prove your identity.",
"Single Sign On": "Single Sign On",
"Confirm adding email": "Confirm adding email",
"Click the button below to confirm adding this email address.": "Click the button below to confirm adding this email address.",
"Confirm": "Confirm",
"Add Email Address": "Add Email Address", "Add Email Address": "Add Email Address",
"Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email", "Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email",
"Confirm adding this phone number by using Single Sign On to prove your identity.": "Confirm adding this phone number by using Single Sign On to prove your identity.",
"Confirm adding phone number": "Confirm adding phone number",
"Click the button below to confirm adding this phone number.": "Click the button below to confirm adding this phone number.",
"Add Phone Number": "Add Phone Number", "Add Phone Number": "Add Phone Number",
"The platform you're on": "The platform you're on", "The platform you're on": "The platform you're on",
"The version of Riot": "The version of Riot", "The version of Riot": "The version of Riot",
@ -599,6 +608,10 @@
"up to date": "up to date", "up to date": "up to date",
"Your homeserver does not support session management.": "Your homeserver does not support session management.", "Your homeserver does not support session management.": "Your homeserver does not support session management.",
"Unable to load session list": "Unable to load session list", "Unable to load session list": "Unable to load session list",
"Confirm deleting these sessions by using Single Sign On to prove your identity.": "Confirm deleting these sessions by using Single Sign On to prove your identity.",
"Confirm deleting these sessions": "Confirm deleting these sessions",
"Click the button below to confirm deleting these sessions.": "Click the button below to confirm deleting these sessions.",
"Delete sessions": "Delete sessions",
"Authentication": "Authentication", "Authentication": "Authentication",
"Delete %(count)s sessions|other": "Delete %(count)s sessions", "Delete %(count)s sessions|other": "Delete %(count)s sessions",
"Delete %(count)s sessions|one": "Delete %(count)s session", "Delete %(count)s sessions|one": "Delete %(count)s session",
@ -1862,7 +1875,6 @@
"Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only", "Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only",
"Enter username": "Enter username", "Enter username": "Enter username",
"Email (optional)": "Email (optional)", "Email (optional)": "Email (optional)",
"Confirm": "Confirm",
"Phone (optional)": "Phone (optional)", "Phone (optional)": "Phone (optional)",
"Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s",
"Create your Matrix account on <underlinedServerName />": "Create your Matrix account on <underlinedServerName />", "Create your Matrix account on <underlinedServerName />": "Create your Matrix account on <underlinedServerName />",

View file

@ -109,6 +109,7 @@ export default class EventIndex extends EventEmitter {
roomId: room.roomId, roomId: room.roomId,
token: token, token: token,
direction: "b", direction: "b",
fullCrawl: true,
}; };
const forwardCheckpoint = { const forwardCheckpoint = {

View file

@ -30,26 +30,6 @@ import ActiveWidgetStore from "../stores/ActiveWidgetStore";
import {IntegrationManagers} from "../integrations/IntegrationManagers"; import {IntegrationManagers} from "../integrations/IntegrationManagers";
import {Capability} from "../widgets/WidgetApi"; import {Capability} from "../widgets/WidgetApi";
/**
* Encodes a URI according to a set of template variables. Variables will be
* passed through encodeURIComponent.
* @param {string} pathTemplate 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 encodeUri(pathTemplate, variables) {
for (const key in variables) {
if (!variables.hasOwnProperty(key)) {
continue;
}
pathTemplate = pathTemplate.replace(
key, encodeURIComponent(variables[key]),
);
}
return pathTemplate;
}
export default class WidgetUtils { export default class WidgetUtils {
/* Returns true if user is able to send state events to modify widgets in this room /* Returns true if user is able to send state events to modify widgets in this room
* (Does not apply to non-room-based / user widgets) * (Does not apply to non-room-based / user widgets)
@ -402,18 +382,6 @@ export default class WidgetUtils {
} }
static makeAppConfig(appId, app, senderUserId, roomId, eventId) { static makeAppConfig(appId, app, senderUserId, roomId, eventId) {
const myUserId = MatrixClientPeg.get().credentials.userId;
const user = MatrixClientPeg.get().getUser(myUserId);
const params = {
'$matrix_user_id': myUserId,
'$matrix_room_id': roomId,
'$matrix_display_name': user ? user.displayName : myUserId,
'$matrix_avatar_url': user ? MatrixClientPeg.get().mxcUrlToHttp(user.avatarUrl) : '',
// TODO: Namespace themes through some standard
'$theme': SettingsStore.getValue("theme"),
};
if (!senderUserId) { if (!senderUserId) {
throw new Error("Widgets must be created by someone - provide a senderUserId"); throw new Error("Widgets must be created by someone - provide a senderUserId");
} }
@ -423,32 +391,6 @@ export default class WidgetUtils {
app.eventId = eventId; app.eventId = eventId;
app.name = app.name || app.type; app.name = app.name || app.type;
if (app.type === 'jitsi') {
console.log("Replacing Jitsi widget URL with local wrapper");
if (!app.data || !app.data.conferenceId) {
// Assumed to be a v1 widget: add a data object for visibility on the wrapper
// TODO: Remove this once mobile supports v2 widgets
console.log("Replacing v1 Jitsi widget with v2 equivalent");
const parsed = new URL(app.url);
app.data = {
conferenceId: parsed.searchParams.get("confId"),
domain: "jitsi.riot.im", // v1 widgets have this hardcoded
};
}
app.url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true});
}
if (app.data) {
Object.keys(app.data).forEach((key) => {
params['$' + key] = app.data[key];
});
app.waitForIframeLoad = (app.data.waitForIframeLoad === 'false' ? false : true);
}
app.url = encodeUri(app.url, params);
return app; return app;
} }
@ -462,7 +404,6 @@ export default class WidgetUtils {
// widgets from at all, but it probably makes sense for sanity. // widgets from at all, but it probably makes sense for sanity.
if (appType === 'jitsi') { if (appType === 'jitsi') {
capWhitelist.push(Capability.AlwaysOnScreen); capWhitelist.push(Capability.AlwaysOnScreen);
capWhitelist.push(Capability.GetRiotWebConfig);
} }
return capWhitelist; return capWhitelist;

View file

@ -99,7 +99,7 @@ export async function legacyVerifyUser(user) {
return; return;
} }
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const verificationRequestPromise = cli.beginKeyVerification(user.userId); const verificationRequestPromise = cli.requestVerification(user.userId);
dis.dispatch({ dis.dispatch({
action: "set_right_panel_phase", action: "set_right_panel_phase",
phase: RIGHT_PANEL_PHASES.EncryptionPanel, phase: RIGHT_PANEL_PHASES.EncryptionPanel,

View file

@ -23,7 +23,6 @@ export enum Capability {
Screenshot = "m.capability.screenshot", Screenshot = "m.capability.screenshot",
Sticker = "m.sticker", Sticker = "m.sticker",
AlwaysOnScreen = "m.always_on_screen", AlwaysOnScreen = "m.always_on_screen",
GetRiotWebConfig = "im.vector.web.riot_config",
} }
export enum KnownWidgetActions { export enum KnownWidgetActions {
@ -34,7 +33,6 @@ export enum KnownWidgetActions {
UpdateVisibility = "visibility", UpdateVisibility = "visibility",
ReceiveOpenIDCredentials = "openid_credentials", ReceiveOpenIDCredentials = "openid_credentials",
SetAlwaysOnScreen = "set_always_on_screen", SetAlwaysOnScreen = "set_always_on_screen",
GetRiotWebConfig = "im.vector.web.riot_config",
ClientReady = "im.vector.ready", ClientReady = "im.vector.ready",
} }
@ -157,12 +155,4 @@ export class WidgetApi {
resolve(); // SetAlwaysOnScreen is currently fire-and-forget, but that could change. resolve(); // SetAlwaysOnScreen is currently fire-and-forget, but that could change.
}); });
} }
public getRiotConfig(): Promise<any> {
return new Promise<any>(resolve => {
this.callAction(KnownWidgetActions.GetRiotWebConfig, {}, response => {
resolve(response.response.config);
});
});
}
} }

Some files were not shown because too many files have changed in this diff Show more