diff --git a/res/css/_components.scss b/res/css/_components.scss
index 25b7c9342e..0603296ef5 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -52,10 +52,12 @@
@import "./views/dialogs/_EncryptedEventDialog.scss";
@import "./views/dialogs/_GroupAddressPicker.scss";
@import "./views/dialogs/_RestoreKeyBackupDialog.scss";
+@import "./views/dialogs/_RoomSettingsDialog.scss";
@import "./views/dialogs/_RoomUpgradeDialog.scss";
@import "./views/dialogs/_SetEmailDialog.scss";
@import "./views/dialogs/_SetMxIdDialog.scss";
@import "./views/dialogs/_SetPasswordDialog.scss";
+@import "./views/dialogs/_SettingsDialog.scss";
@import "./views/dialogs/_ShareDialog.scss";
@import "./views/dialogs/_UnknownDeviceDialog.scss";
@import "./views/dialogs/_UserSettingsDialog.scss";
@@ -70,6 +72,7 @@
@import "./views/elements/_Dropdown.scss";
@import "./views/elements/_EditableItemList.scss";
@import "./views/elements/_Field.scss";
+@import "./views/elements/_HexVerify.scss";
@import "./views/elements/_ImageView.scss";
@import "./views/elements/_InlineSpinner.scss";
@import "./views/elements/_MemberEventListSummary.scss";
@@ -136,7 +139,9 @@
@import "./views/settings/_PhoneNumbers.scss";
@import "./views/settings/_ProfileSettings.scss";
@import "./views/settings/tabs/_GeneralSettingsTab.scss";
+@import "./views/settings/tabs/_HelpSettingsTab.scss";
@import "./views/settings/tabs/_PreferencesSettingsTab.scss";
+@import "./views/settings/tabs/_SecuritySettingsTab.scss";
@import "./views/settings/tabs/_SettingsTab.scss";
@import "./views/settings/tabs/_VoiceSettingsTab.scss";
@import "./views/voip/_CallView.scss";
diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss
index faaf1cf462..e403057cd3 100644
--- a/res/css/structures/_RoomSubList.scss
+++ b/res/css/structures/_RoomSubList.scss
@@ -32,34 +32,13 @@ limitations under the License.
*/
.mx_RoomSubList {
- min-height: 31px;
- flex: 0 10000 auto;
display: flex;
flex-direction: column;
}
-.mx_RoomSubList.resized-sized {
- /*
- flex-basis to 0 so sublists
- are not shrinking/growing relative
- to their content (as would be the case with auto),
- as this intervenes with sizing an item exactly
- when not available space is available
- in the flex container
- */
- flex: 1 1 0;
-}
-.mx_RoomSubList_nonEmpty {
- min-height: 74px;
-
- .mx_AutoHideScrollbar_offset {
- padding-bottom: 4px;
- }
-}
-
-.mx_RoomSubList_hidden {
- flex: none !important;
+.mx_RoomSubList_nonEmpty .mx_AutoHideScrollbar_offset {
+ padding-bottom: 4px;
}
.mx_RoomSubList_labelContainer {
diff --git a/res/css/structures/_TabbedView.scss b/res/css/structures/_TabbedView.scss
index d9d66f470a..d1a448711a 100644
--- a/res/css/structures/_TabbedView.scss
+++ b/res/css/structures/_TabbedView.scss
@@ -24,7 +24,8 @@ limitations under the License.
}
.mx_TabbedView_tabLabels {
- width: 136px;
+ width: 150px;
+ max-width: 150px;
height: 100%;
color: $tab-label-fg-color;
}
@@ -36,7 +37,7 @@ limitations under the License.
border-radius: 3px;
font-size: 12px;
font-weight: 600;
- height: 20px;
+ min-height: 20px; // use min-height instead of height to allow the label to overflow a bit
margin-bottom: 6px;
position: relative;
}
diff --git a/res/css/views/dialogs/_RoomSettingsDialog.scss b/res/css/views/dialogs/_RoomSettingsDialog.scss
new file mode 100644
index 0000000000..f255bb38f0
--- /dev/null
+++ b/res/css/views/dialogs/_RoomSettingsDialog.scss
@@ -0,0 +1,34 @@
+/*
+Copyright 2019 New Vector Ltd.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// ICONS
+// ==========================================================
+
+.mx_RoomSettingsDialog_settingsIcon:before {
+ mask-image: url('$(res)/img/feather-icons/settings.svg');
+}
+
+.mx_RoomSettingsDialog_securityIcon:before {
+ mask-image: url('$(res)/img/feather-icons/lock.svg');
+}
+
+.mx_RoomSettingsDialog_rolesIcon:before {
+ mask-image: url('$(res)/img/feather-icons/users-sm.svg');
+}
+
+.mx_RoomSettingsDialog_warningIcon:before {
+ mask-image: url('$(res)/img/feather-icons/warning-triangle.svg');
+}
diff --git a/res/css/views/dialogs/_SettingsDialog.scss b/res/css/views/dialogs/_SettingsDialog.scss
new file mode 100644
index 0000000000..2cc7c08039
--- /dev/null
+++ b/res/css/views/dialogs/_SettingsDialog.scss
@@ -0,0 +1,70 @@
+/*
+Copyright 2019 New Vector Ltd.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_SettingsDialog {
+ .mx_Dialog {
+ max-width: 784px; // 900px - 58px (left padding) - 58px (right padding)
+ width: 80%;
+ height: 80%;
+ border-radius: 4px;
+
+ .mx_TabbedView_tabLabels {
+ // Force the sidebar to be always visible, letting the rest of the content scroll
+ position: fixed;
+ }
+
+ .mx_TabbedView_tabPanel {
+ max-width: 485px;
+ margin-left: 206px; // 70px margin + 136px for the sidebar
+ }
+
+ .mx_SettingsDialog_header {
+ font-size: 24px;
+ display: block;
+ text-align: center;
+ color: $dialog-title-fg-color;
+ margin-top: 16px;
+ margin-bottom: 24px;
+ padding: 0;
+ }
+
+ .mx_SettingsDialog_close {
+ position: absolute;
+ top: 16px;
+ right: 25px;
+ }
+
+ .mx_SettingsDialog_closeIcon {
+ width: 16px;
+ height: 16px;
+ display: inline-block;
+ }
+
+ .mx_SettingsDialog_closeIcon:before {
+ mask: url('$(res)/img/feather-icons/cancel.svg');
+ background-color: $dialog-close-fg-color;
+ mask-repeat: no-repeat;
+ mask-size: 16px;
+ mask-position: center;
+ content: '';
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ }
+ }
+}
diff --git a/res/css/views/dialogs/_UserSettingsDialog.scss b/res/css/views/dialogs/_UserSettingsDialog.scss
index d7a31d0c99..2849573790 100644
--- a/res/css/views/dialogs/_UserSettingsDialog.scss
+++ b/res/css/views/dialogs/_UserSettingsDialog.scss
@@ -1,39 +1,18 @@
-.mx_UserSettingsDialog_header {
- font-size: 24px;
- display: block;
- text-align: center;
- color: $dialog-title-fg-color;
- margin-top: 16px;
- margin-bottom: 24px;
- padding: 0;
-}
+/*
+Copyright 2019 New Vector Ltd.
-.mx_UserSettingsDialog_close {
- position: absolute;
- top: 16px;
- right: 25px;
-}
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
-.mx_UserSettingsDialog_closeIcon {
- width: 16px;
- height: 16px;
- display: inline-block;
-}
-
-.mx_UserSettingsDialog_closeIcon:before {
- mask: url('$(res)/img/feather-icons/cancel.svg');
- background-color: $dialog-close-fg-color;
- mask-repeat: no-repeat;
- mask-size: 16px;
- mask-position: center;
- content: '';
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
-}
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
// ICONS
// ==========================================================
diff --git a/res/css/views/elements/_HexVerify.scss b/res/css/views/elements/_HexVerify.scss
new file mode 100644
index 0000000000..3f3ee4b7ea
--- /dev/null
+++ b/res/css/views/elements/_HexVerify.scss
@@ -0,0 +1,34 @@
+/*
+Copyright 2019 New Vector Ltd.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_HexVerify {
+ text-align: center;
+}
+
+.mx_HexVerify_pair {
+ display: inline-block;
+ font-weight: bold;
+ padding-left: 3px;
+ padding-right: 3px;
+}
+
+.mx_HexVerify_pair_verified {
+ color: $accent-color;
+}
+
+.mx_HexVerify_pair:hover{
+ color: $accent-color;
+}
diff --git a/res/css/views/elements/_ToggleSwitch.scss b/res/css/views/elements/_ToggleSwitch.scss
index 0955648d50..4076414086 100644
--- a/res/css/views/elements/_ToggleSwitch.scss
+++ b/res/css/views/elements/_ToggleSwitch.scss
@@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-// TODO: Fancy transitions
-
.mx_ToggleSwitch {
+ transition: background-color 0.20s ease-out 0.1s;
width: 48px;
height: 24px;
border-radius: 14px;
@@ -33,6 +32,7 @@ limitations under the License.
}
.mx_ToggleSwitch_ball {
+ transition: left 0.15s ease-out 0.1s;
margin: 2px;
width: 20px;
height: 20px;
@@ -47,5 +47,5 @@ limitations under the License.
}
.mx_ToggleSwitch_on > .mx_ToggleSwitch_ball {
- right: 2px;
+ left: 23px; // 48px switch - 20px ball - 5px padding = 23px
}
diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss
index 8f78e3bb7a..360966a952 100644
--- a/res/css/views/rooms/_RoomList.scss
+++ b/res/css/views/rooms/_RoomList.scss
@@ -17,13 +17,14 @@ limitations under the License.
.mx_RoomList {
/* take up remaining space below TopLeftMenu */
- flex: 1 1 auto;
- /* use flexbox to layout sublists */
- display: flex;
- flex-direction: column;
+ flex: 1;
min-height: 0;
}
+.mx_RoomList .mx_ResizeHandle {
+ position: relative;
+}
+
.mx_SearchBox {
flex: none;
}
diff --git a/res/css/views/settings/tabs/_HelpSettingsTab.scss b/res/css/views/settings/tabs/_HelpSettingsTab.scss
new file mode 100644
index 0000000000..a1199b59b6
--- /dev/null
+++ b/res/css/views/settings/tabs/_HelpSettingsTab.scss
@@ -0,0 +1,24 @@
+/*
+Copyright 2019 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_HelpSettingsTab_debugButton {
+ margin-bottom: 5px;
+ margin-top: 5px;
+}
+
+.mx_HelpSettingsTab span.mx_AccessibleButton {
+ word-break: break-word;
+}
\ No newline at end of file
diff --git a/res/css/views/settings/tabs/_SecuritySettingsTab.scss b/res/css/views/settings/tabs/_SecuritySettingsTab.scss
new file mode 100644
index 0000000000..2640df1383
--- /dev/null
+++ b/res/css/views/settings/tabs/_SecuritySettingsTab.scss
@@ -0,0 +1,53 @@
+/*
+Copyright 2019 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_SecuritySettingsTab .mx_DevicesPanel {
+ // Normally the panel is 880px, however this can easily overflow the container.
+ // TODO: Fix the table to not be squishy
+ width: auto;
+ max-width: 880px;
+}
+
+.mx_SecuritySettingsTab_deviceInfo {
+ display: table;
+ padding-left: 0;
+}
+
+.mx_SecuritySettingsTab_deviceInfo > li {
+ display: table-row;
+}
+
+.mx_SecuritySettingsTab_deviceInfo > li > label,
+.mx_SecuritySettingsTab_deviceInfo > li > span {
+ display: table-cell;
+ padding-right: 1em;
+}
+
+.mx_SecuritySettingsTab_importExportButtons .mx_AccessibleButton {
+ margin-right: 10px;
+}
+
+.mx_SecuritySettingsTab_importExportButtons {
+ margin-bottom: 15px;
+}
+
+.mx_SecuritySettingsTab_ignoredUser {
+ margin-bottom: 5px;
+}
+
+.mx_SecuritySettingsTab_ignoredUser .mx_AccessibleButton {
+ margin-right: 10px;
+}
\ No newline at end of file
diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss
index 1a26c58c44..17f869b5b0 100644
--- a/res/css/views/settings/tabs/_SettingsTab.scss
+++ b/res/css/views/settings/tabs/_SettingsTab.scss
@@ -26,15 +26,15 @@ limitations under the License.
font-family: $font-family-semibold;
color: $primary-fg-color;
margin-bottom: 10px;
- margin-top: 10px;
+ margin-top: 12px;
}
.mx_SettingsTab_subsectionText {
color: $settings-subsection-fg-color;
font-size: 12px;
padding-bottom: 12px;
- margin: 0;
display: block;
+ margin: 0 100px 0 0; // Align with the rest of the view
}
.mx_SettingsTab_section .mx_SettingsFlag {
@@ -54,3 +54,9 @@ limitations under the License.
.mx_SettingsTab_section .mx_SettingsFlag .mx_ToggleSwitch {
float: right;
}
+
+.mx_SettingsTab_linkBtn {
+ cursor: pointer;
+ color: $accent-color;
+ word-break: break-all;
+}
\ No newline at end of file
diff --git a/res/img/feather-icons/users-sm.svg b/res/img/feather-icons/users-sm.svg
new file mode 100644
index 0000000000..6098be38c3
--- /dev/null
+++ b/res/img/feather-icons/users-sm.svg
@@ -0,0 +1,7 @@
+
diff --git a/res/img/feather-icons/warning-triangle.svg b/res/img/feather-icons/warning-triangle.svg
new file mode 100644
index 0000000000..02196cbf43
--- /dev/null
+++ b/res/img/feather-icons/warning-triangle.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index ed057eb020..54ac605c65 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -27,7 +27,6 @@ import UserActivity from './UserActivity';
import Presence from './Presence';
import dis from './dispatcher';
import DMRoomMap from './utils/DMRoomMap';
-import RtsClient from './RtsClient';
import Modal from './Modal';
import sdk from './index';
import ActiveWidgetStore from './stores/ActiveWidgetStore';
@@ -224,7 +223,7 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
//
// The plan is to gradually move the localStorage access done here into
// SessionStore to avoid bugs where the view becomes out-of-sync with
-// localStorage (e.g. teamToken, isGuest etc.)
+// localStorage (e.g. isGuest etc.)
async function _restoreFromLocalStorage() {
if (!localStorage) {
return false;
@@ -286,15 +285,6 @@ function _handleLoadSessionFailure(e) {
});
}
-let rtsClient = null;
-export function initRtsClient(url) {
- if (url) {
- rtsClient = new RtsClient(url);
- } else {
- rtsClient = null;
- }
-}
-
/**
* Transitions to a logged-in state using the given credentials.
*
@@ -333,7 +323,7 @@ async function _doSetLoggedIn(credentials, clearStorage) {
);
// This is dispatched to indicate that the user is still in the process of logging in
- // because `teamPromise` may take some time to resolve, breaking the assumption that
+ // because async code may take some time to resolve, breaking the assumption that
// `setLoggedIn` takes an "instant" to complete, and dispatch `on_logged_in` a few ms
// later than MatrixChat might assume.
//
@@ -347,10 +337,6 @@ async function _doSetLoggedIn(credentials, clearStorage) {
Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl, credentials.identityServerUrl);
- // Resolves by default
- let teamPromise = Promise.resolve(null);
-
-
if (localStorage) {
try {
_persistCredentialsToLocalStorage(credentials);
@@ -367,27 +353,13 @@ async function _doSetLoggedIn(credentials, clearStorage) {
} catch (e) {
console.warn("Error using local storage: can't persist session!", e);
}
-
- if (rtsClient && !credentials.guest) {
- teamPromise = rtsClient.login(credentials.userId).then((body) => {
- if (body.team_token) {
- localStorage.setItem("mx_team_token", body.team_token);
- }
- return body.team_token;
- }, (err) => {
- console.warn(`Failed to get team token on login: ${err}` );
- return null;
- });
- }
} else {
console.warn("No local storage available: can't persist session!");
}
MatrixClientPeg.replaceUsingCreds(credentials);
- teamPromise.then((teamToken) => {
- dis.dispatch({action: 'on_logged_in', teamToken: teamToken});
- });
+ dis.dispatch({ action: 'on_logged_in' });
await startMatrixClient();
return MatrixClientPeg.get();
diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js
index 16ae22a4d9..b216f5740c 100644
--- a/src/MatrixClientPeg.js
+++ b/src/MatrixClientPeg.js
@@ -29,6 +29,7 @@ import SettingsStore from './settings/SettingsStore';
import MatrixActionCreators from './actions/MatrixActionCreators';
import {phasedRollOutExpiredForUser} from "./PhasedRollOut";
import Modal from './Modal';
+import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
interface MatrixClientCreds {
homeserverUrl: string,
@@ -184,6 +185,7 @@ class MatrixClientPeg {
deviceId: creds.deviceId,
timelineSupport: true,
forceTURN: !SettingsStore.getValue('webRtcForcePeerToPeer', false),
+ verificationMethods: [verificationMethods.SAS]
};
this.matrixClient = createMatrixClient(opts, useIndexedDb);
diff --git a/src/RtsClient.js b/src/RtsClient.js
deleted file mode 100644
index 493b19599c..0000000000
--- a/src/RtsClient.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import 'whatwg-fetch';
-
-let fetchFunction = fetch;
-
-function checkStatus(response) {
- if (!response.ok) {
- return response.text().then((text) => {
- throw new Error(text);
- });
- }
- return response;
-}
-
-function parseJson(response) {
- return response.json();
-}
-
-function encodeQueryParams(params) {
- return '?' + Object.keys(params).map((k) => {
- return k + '=' + encodeURIComponent(params[k]);
- }).join('&');
-}
-
-const request = (url, opts) => {
- if (opts && opts.qs) {
- url += encodeQueryParams(opts.qs);
- delete opts.qs;
- }
- if (opts && opts.body) {
- if (!opts.headers) {
- opts.headers = {};
- }
- opts.body = JSON.stringify(opts.body);
- opts.headers['Content-Type'] = 'application/json';
- }
- return fetchFunction(url, opts)
- .then(checkStatus)
- .then(parseJson);
-};
-
-
-export default class RtsClient {
- constructor(url) {
- this._url = url;
- }
-
- getTeamsConfig() {
- return request(this._url + '/teams');
- }
-
- /**
- * Track a referral with the Riot Team Server. This should be called once a referred
- * user has been successfully registered.
- * @param {string} referrer the user ID of one who referred the user to Riot.
- * @param {string} sid the sign-up identity server session ID .
- * @param {string} clientSecret the sign-up client secret.
- * @returns {Promise} a promise that resolves to { team_token: 'sometoken' } upon
- * success.
- */
- trackReferral(referrer, sid, clientSecret) {
- return request(this._url + '/register',
- {
- body: {
- referrer: referrer,
- session_id: sid,
- client_secret: clientSecret,
- },
- method: 'POST',
- },
- );
- }
-
- getTeam(teamToken) {
- return request(this._url + '/teamConfiguration',
- {
- qs: {
- team_token: teamToken,
- },
- },
- );
- }
-
- /**
- * Signal to the RTS that a login has occurred and that a user requires their team's
- * token.
- * @param {string} userId the user ID of the user who is a member of a team.
- * @returns {Promise} a promise that resolves to { team_token: 'sometoken' } upon
- * success.
- */
- login(userId) {
- return request(this._url + '/login',
- {
- qs: {
- user_id: userId,
- },
- },
- );
- }
-
- // allow fetch to be replaced, for testing.
- static setFetch(fn) {
- fetchFunction = fn;
- }
-}
diff --git a/src/components/structures/HomePage.js b/src/components/structures/HomePage.js
index aa17e63d73..948a07ee59 100644
--- a/src/components/structures/HomePage.js
+++ b/src/components/structures/HomePage.js
@@ -30,11 +30,6 @@ class HomePage extends React.Component {
static displayName = 'HomePage';
static propTypes = {
- // URL base of the team server. Optional.
- teamServerUrl: PropTypes.string,
- // Team token. Optional. If set, used to get the static homepage of the team
- // associated. If unset, homePageUrl will be used.
- teamToken: PropTypes.string,
// URL to use as the iFrame src. Defaults to /home.html.
homePageUrl: PropTypes.string,
};
@@ -56,35 +51,29 @@ class HomePage extends React.Component {
componentWillMount() {
this._unmounted = false;
- if (this.props.teamToken && this.props.teamServerUrl) {
- this.setState({
- iframeSrc: `${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html`,
- });
- } else {
- // we use request() to inline the homepage into the react component
- // so that it can inherit CSS and theming easily rather than mess around
- // with iframes and trying to synchronise document.stylesheets.
+ // we use request() to inline the homepage into the react component
+ // so that it can inherit CSS and theming easily rather than mess around
+ // with iframes and trying to synchronise document.stylesheets.
- const src = this.props.homePageUrl || 'home.html';
+ const src = this.props.homePageUrl || 'home.html';
- request(
- { method: "GET", url: src },
- (err, response, body) => {
- if (this._unmounted) {
- return;
- }
+ request(
+ { method: "GET", url: src },
+ (err, response, body) => {
+ if (this._unmounted) {
+ return;
+ }
- if (err || response.status < 200 || response.status >= 300) {
- console.warn(`Error loading home page: ${err}`);
- this.setState({ page: _t("Couldn't load home page") });
- return;
- }
+ if (err || response.status < 200 || response.status >= 300) {
+ console.warn(`Error loading home page: ${err}`);
+ this.setState({ page: _t("Couldn't load home page") });
+ return;
+ }
- body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
- this.setState({ page: body });
- },
- );
- }
+ body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
+ this.setState({ page: body });
+ },
+ );
}
componentWillUnmount() {
diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js
index 4bab919413..489e9bd243 100644
--- a/src/components/structures/LeftPanel.js
+++ b/src/components/structures/LeftPanel.js
@@ -212,6 +212,7 @@ const LeftPanel = React.createClass({
diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js
index 6f2e1f3989..2fd7a9bccf 100644
--- a/src/components/structures/LoggedInView.js
+++ b/src/components/structures/LoggedInView.js
@@ -63,7 +63,6 @@ const LoggedInView = React.createClass({
// transitioned to PWLU)
onRegistered: PropTypes.func,
collapsedRhs: PropTypes.bool,
- teamToken: PropTypes.string,
// Used by the RoomView to handle joining rooms
viaServers: PropTypes.arrayOf(PropTypes.string),
@@ -457,8 +456,6 @@ const LoggedInView = React.createClass({
pageElement = ;
break;
@@ -475,15 +472,7 @@ const LoggedInView = React.createClass({
case PageTypes.HomePage:
{
- // If team server config is present, pass the teamServerURL. props.teamToken
- // must also be set for the team page to be displayed, otherwise the
- // welcomePageUrl is used (which might be undefined).
- const teamServerUrl = this.props.config.teamServerConfig ?
- this.props.config.teamServerConfig.teamServerURL : null;
-
pageElement = ;
}
@@ -556,6 +545,7 @@ const LoggedInView = React.createClass({
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index d5ad130edd..b8be3e017a 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -75,8 +75,8 @@ const VIEWS = {
// we have valid matrix credentials (either via an explicit login, via the
// initial re-animation/guest registration, or via a registration), and are
// now setting up a matrixclient to talk to it. This isn't an instant
- // process because (a) we need to clear out indexeddb, and (b) we need to
- // talk to the team server; while it is going on we show a big spinner.
+ // process because we need to clear out indexeddb. While it is going on we
+ // show a big spinner.
LOGGING_IN: 5,
// we are logged in with an active matrix client.
@@ -256,42 +256,6 @@ export default React.createClass({
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
}
- // To enable things like riot.im/geektime in a nicer way than rewriting the URL
- // and appending a team token query parameter, use the first path segment to
- // indicate a team, with "public" team tokens stored in the config teamTokenMap.
- let routedTeamToken = null;
- if (this.props.config.teamTokenMap) {
- const teamName = window.location.pathname.split('/')[1];
- if (teamName && this.props.config.teamTokenMap.hasOwnProperty(teamName)) {
- routedTeamToken = this.props.config.teamTokenMap[teamName];
- }
- }
-
- // Persist the team token across refreshes using sessionStorage. A new window or
- // tab will not persist sessionStorage, but refreshes will.
- if (this.props.startingFragmentQueryParams.team_token) {
- window.sessionStorage.setItem(
- 'mx_team_token',
- this.props.startingFragmentQueryParams.team_token,
- );
- }
-
- // Use the locally-stored team token first, then as a fall-back, check to see if
- // a referral link was used, which will contain a query parameter `team_token`.
- this._teamToken = routedTeamToken ||
- window.localStorage.getItem('mx_team_token') ||
- window.sessionStorage.getItem('mx_team_token');
-
- // Some users have ended up with "undefined" as their local storage team token,
- // treat that as undefined.
- if (this._teamToken === "undefined") {
- this._teamToken = undefined;
- }
-
- if (this._teamToken) {
- console.info(`Team token set to ${this._teamToken}`);
- }
-
// Set up the default URLs (async)
if (this.getDefaultServerName() && !this.getDefaultHsUrl(false)) {
this.setState({loadingDefaultHomeserver: true});
@@ -360,9 +324,6 @@ export default React.createClass({
linkifyMatrix.onGroupClick = this.onGroupClick;
}
- const teamServerConfig = this.props.config.teamServerConfig || {};
- Lifecycle.initRtsClient(teamServerConfig.teamServerURL);
-
// the first thing to do is to try the token params in the query-string
Lifecycle.attemptTokenLogin(this.props.realQueryParams).then((loggedIn) => {
if (loggedIn) {
@@ -613,7 +574,7 @@ export default React.createClass({
case 'view_user_settings': {
if (SettingsStore.isFeatureEnabled("feature_tabbed_settings")) {
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
- Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {});
+ Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {}, 'mx_SettingsDialog');
} else {
this._setPage(PageTypes.UserSettings);
this.notifyNewScreen('settings');
@@ -726,7 +687,7 @@ export default React.createClass({
});
break;
case 'on_logged_in':
- this._onLoggedIn(payload.teamToken);
+ this._onLoggedIn();
break;
case 'on_logged_out':
this._onLoggedOut();
@@ -1196,16 +1157,10 @@ export default React.createClass({
/**
* Called when a new logged in session has started
- *
- * @param {string} teamToken
*/
- _onLoggedIn: async function(teamToken) {
+ _onLoggedIn: async function() {
this.setStateForNewView({view: VIEWS.LOGGED_IN});
- if (teamToken) {
- // A team member has logged in, not a guest
- this._teamToken = teamToken;
- dis.dispatch({action: 'view_home_page'});
- } else if (this._is_registered) {
+ if (this._is_registered) {
this._is_registered = false;
if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) {
@@ -1261,7 +1216,6 @@ export default React.createClass({
currentRoomId: null,
page_type: PageTypes.RoomDirectory,
});
- this._teamToken = null;
this._setPageSubtitle();
},
@@ -1278,6 +1232,7 @@ export default React.createClass({
this.firstSyncComplete = false;
this.firstSyncPromise = Promise.defer();
const cli = MatrixClientPeg.get();
+ const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
// Allow the JS SDK to reap timeline events. This reduces the amount of
// memory consumed as the JS SDK stores multiple distinct copies of room
@@ -1477,6 +1432,12 @@ export default React.createClass({
}
});
+ cli.on("crypto.verification.start", (verifier) => {
+ Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
+ verifier,
+ });
+ });
+
// Fire the tinter right on startup to ensure the default theme is applied
// A later sync can/will correct the tint to be the right value for the user
const colorScheme = SettingsStore.getValue("roomColor");
@@ -1707,15 +1668,13 @@ export default React.createClass({
onReturnToAppClick: function() {
// treat it the same as if the user had completed the login
- this._onLoggedIn(null);
+ this._onLoggedIn();
},
// returns a promise which resolves to the new MatrixClient
- onRegistered: function(credentials, teamToken) {
- // XXX: These both should be in state or ideally store(s) because we risk not
+ onRegistered: function(credentials) {
+ // XXX: This should be in state or ideally store(s) because we risk not
// rendering the most up-to-date view of state otherwise.
- // teamToken may not be truthy
- this._teamToken = teamToken;
this._is_registered = true;
return Lifecycle.setLoggedIn(credentials);
},
@@ -1888,7 +1847,6 @@ export default React.createClass({
onCloseAllSettings={this.onCloseAllSettings}
onRegistered={this.onRegistered}
currentRoomId={this.state.currentRoomId}
- teamToken={this._teamToken}
showCookieBar={this.state.showCookieBar}
{...this.props}
{...this.state}
@@ -1929,7 +1887,6 @@ export default React.createClass({
defaultHsUrl={this.getDefaultHsUrl()}
defaultIsUrl={this.getDefaultIsUrl()}
brand={this.props.config.brand}
- teamServerConfig={this.props.config.teamServerConfig}
customHsUrl={this.getCurrentHsUrl()}
customIsUrl={this.getCurrentIsUrl()}
makeRegistrationUrl={this._makeRegistrationUrl}
diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js
index cda1c9967e..852dddd063 100644
--- a/src/components/structures/RoomSubList.js
+++ b/src/components/structures/RoomSubList.js
@@ -313,6 +313,12 @@ const RoomSubList = React.createClass({
}
},
+ setHeight: function(height) {
+ if (this.refs.subList) {
+ this.refs.subList.style.height = `${height}px`;
+ }
+ },
+
render: function() {
const len = this.props.list.length + this.props.extraTiles.length;
if (len) {
@@ -322,13 +328,13 @@ const RoomSubList = React.createClass({
"mx_RoomSubList_nonEmpty": len && !this.state.hidden,
});
if (this.state.hidden) {
- return
);
- let belowEmailSection;
- if (this.props.teamsConfig) {
- if (this.props.teamsConfig.supportEmail && this.state.showSupportEmail) {
- belowEmailSection = (
-
- Sorry, but your university is not registered with us just yet.
- Email us on
-
- { this.props.teamsConfig.supportEmail }
-
- to get your university signed up.
- Or continue to register with Riot to enjoy our open source platform.
-
+ { _t("To verify that this device can be trusted, please contact its " +
+ "owner using some other means (e.g. in person or a phone call) " +
+ "and ask them whether the key they see in their User Settings " +
+ "for this device matches the key below:") }
+
+
+
+
{ this.props.device.getDisplayName() }
+
{ this.props.device.deviceId }
+
{ key }
+
+
+
+ { _t("If it matches, press the verify button below. " +
+ "If it doesn't, then someone else is intercepting this device " +
+ "and you probably want to press the blacklist button instead.") }
+
;
+ }
+ }
}
-DeviceVerifyDialog.propTypes = {
- userId: PropTypes.string.isRequired,
- device: PropTypes.object.isRequired,
- onFinished: PropTypes.func.isRequired,
-};
diff --git a/src/components/views/dialogs/IncomingSasDialog.js b/src/components/views/dialogs/IncomingSasDialog.js
new file mode 100644
index 0000000000..2a76e8a904
--- /dev/null
+++ b/src/components/views/dialogs/IncomingSasDialog.js
@@ -0,0 +1,182 @@
+/*
+Copyright 2019 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import sdk from '../../../index';
+import { _t } from '../../../languageHandler';
+
+const PHASE_START = 0;
+const PHASE_SHOW_SAS = 1;
+const PHASE_WAIT_FOR_PARTNER_TO_CONFIRM = 2;
+const PHASE_VERIFIED = 3;
+const PHASE_CANCELLED = 4;
+
+export default class IncomingSasDialog extends React.Component {
+ static propTypes = {
+ verifier: PropTypes.object.isRequired,
+ };
+
+ constructor(props) {
+ super(props);
+
+ this._showSasEvent = null;
+ this.state = {
+ phase: PHASE_START,
+ sasVerified: false,
+ };
+ this.props.verifier.on('show_sas', this._onVerifierShowSas);
+ this.props.verifier.on('cancel', this._onVerifierCancel);
+ }
+
+ componentWillUnmount() {
+ if (this.state.phase !== PHASE_CANCELLED && this.state.phase !== PHASE_VERIFIED) {
+ this.props.verifier.cancel('User cancel');
+ }
+ this.props.verifier.removeListener('show_sas', this._onVerifierShowSas);
+ }
+
+ _onFinished = () => {
+ this.props.onFinished(this.state.phase === PHASE_VERIFIED);
+ }
+
+ _onCancelClick = () => {
+ this.props.onFinished(this.state.phase === PHASE_VERIFIED);
+ }
+
+ _onContinueClick = () => {
+ this.setState({phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM});
+ this.props.verifier.verify().then(() => {
+ this.setState({phase: PHASE_VERIFIED});
+ }).catch((e) => {
+ console.log("Verification failed", e);
+ });
+ }
+
+ _onVerifierShowSas = (e) => {
+ this._showSasEvent = e;
+ this.setState({
+ phase: PHASE_SHOW_SAS,
+ sas: e.sas,
+ });
+ }
+
+ _onVerifierCancel = (e) => {
+ this.setState({
+ phase: PHASE_CANCELLED,
+ });
+ }
+
+ _onSasMatchesClick = () => {
+ this._showSasEvent.confirm();
+ this.setState({
+ phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM,
+ });
+ }
+
+ _onVerifiedDoneClick = () => {
+ this.props.onFinished(true);
+ }
+
+ _renderPhaseStart() {
+ const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
+
+ return (
+
+
{this.props.verifier.userId}
+
{_t(
+ "Verify this user to mark them as trusted. " +
+ "Trusting users gives you extra peace of mind when using " +
+ "end-to-end encrypted messages.",
+ )}
+
{_t(
+ // NB. Below wording adjusted to singular 'device' until we have
+ // cross-signing
+ "Verifying this user will mark their device as trusted, and " +
+ "also mark your device as trusted to them.",
+ )}
+ );
+ }
+
+ _renderPhaseVerified() {
+ const VerificationComplete = sdk.getComponent('views.verification.VerificationComplete');
+ return ;
+ }
+
+ _renderPhaseCancelled() {
+ const VerificationCancelled = sdk.getComponent('views.verification.VerificationCancelled');
+ return ;
+ }
+
+ render() {
+ let body;
+ switch (this.state.phase) {
+ case PHASE_START:
+ body = this._renderPhaseStart();
+ break;
+ case PHASE_SHOW_SAS:
+ body = this._renderPhaseShowSas();
+ break;
+ case PHASE_WAIT_FOR_PARTNER_TO_CONFIRM:
+ body = this._renderPhaseWaitForPartnerToConfirm();
+ break;
+ case PHASE_VERIFIED:
+ body = this._renderPhaseVerified();
+ break;
+ case PHASE_CANCELLED:
+ body = this._renderPhaseCancelled();
+ break;
+ }
+
+ const BaseDialog = sdk.getComponent("dialogs.BaseDialog");
+ return (
+
+ {body}
+
+ );
+ }
+}
+
diff --git a/src/components/views/dialogs/LogoutDialog.js b/src/components/views/dialogs/LogoutDialog.js
index 8c74b3aff5..1f81472cc0 100644
--- a/src/components/views/dialogs/LogoutDialog.js
+++ b/src/components/views/dialogs/LogoutDialog.js
@@ -138,7 +138,9 @@ export default class LogoutDialog extends React.Component {
// once you can restorew a backup by verifying a device
description={_t(
"When signing in again, you can access encrypted chat history by " +
- "restoring your key backup. You'll need your recovery key.",
+ "restoring your key backup. You'll need your recovery passphrase " +
+ "or, if you didn't set a recovery passphrase, your recovery key " +
+ "(that you downloaded).",
)}
button={_t("Sign out")}
onFinished={this._onFinished}
diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js
new file mode 100644
index 0000000000..ce834d564e
--- /dev/null
+++ b/src/components/views/dialogs/RoomSettingsDialog.js
@@ -0,0 +1,90 @@
+/*
+Copyright 2019 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import {Tab, TabbedView} from "../../structures/TabbedView";
+import {_t, _td} from "../../../languageHandler";
+import AccessibleButton from "../elements/AccessibleButton";
+import dis from '../../../dispatcher';
+
+// TODO: Ditch this whole component
+export class TempTab extends React.Component {
+ static propTypes = {
+ onClose: PropTypes.func.isRequired,
+ };
+
+ componentDidMount(): void {
+ dis.dispatch({action: "open_old_room_settings"});
+ this.props.onClose();
+ }
+
+ render() {
+ return
{_t(
+ "Secure Message Recovery has been set up on another device: ",
+ {},
+ {
+ deviceName: () => {this.state.unverifiedDevice.unsigned.device_display_name},
+ },
+ )}
+
{_t(
+ "To view your secure message history and ensure you can view new " +
+ "messages on future devices, verify that device now.",
+ )}
+
;
+ primaryCaption = _t("Verify device");
} else {
// The default case assumes that a key backup doesn't exist for this account.
// (This component doesn't currently check that itself.)
@@ -167,7 +178,7 @@ export default class RoomRecoveryReminder extends React.PureComponent {
- { _t("Set up") }
+ {primaryCaption}
+ );
+ }
+
+ render() {
+ let faqText = _t('For help with using Riot, click here.', {}, {
+ 'a': (sub) => {sub},
+ });
+ if (SdkConfig.get().welcomeUserId && getCurrentLanguage().startsWith('en')) {
+ faqText = (
+
+ {
+ _t('For help with using Riot, click here or start a chat with our ' +
+ 'bot using the button below.', {}, {
+ 'a': (sub) => {sub},
+ })
+ }
+
+ {_t("Start a chat with Riot Bot")}
+
+
+ {
+ _t( "If you've submitted a bug via GitHub, debug logs can help " +
+ "us track down the problem. Debug logs contain application " +
+ "usage data including your username, the IDs or aliases of " +
+ "the rooms or groups you have visited and the usernames of " +
+ "other users. They do not contain messages.",
+ )
+ }
+
+ );
+ }
+}
diff --git a/src/components/views/settings/tabs/SecuritySettingsTab.js b/src/components/views/settings/tabs/SecuritySettingsTab.js
new file mode 100644
index 0000000000..a2a30a2190
--- /dev/null
+++ b/src/components/views/settings/tabs/SecuritySettingsTab.js
@@ -0,0 +1,242 @@
+/*
+Copyright 2019 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import {_t} from "../../../../languageHandler";
+import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore";
+import MatrixClientPeg from "../../../../MatrixClientPeg";
+import * as FormattingUtils from "../../../../utils/FormattingUtils";
+import AccessibleButton from "../../elements/AccessibleButton";
+import Analytics from "../../../../Analytics";
+import Promise from "bluebird";
+import Modal from "../../../../Modal";
+import sdk from "../../../../index";
+
+export class IgnoredUser extends React.Component {
+ static propTypes = {
+ userId: PropTypes.string.isRequired,
+ onUnignored: PropTypes.func.isRequired,
+ };
+
+ _onUnignoreClicked = (e) => {
+ this.props.onUnignored(this.props.userId);
+ };
+
+ render() {
+ return (
+
+
+ {_t('Unignore')}
+
+ {this.props.userId}
+
+ );
+ }
+}
+
+export default class SecuritySettingsTab extends React.Component {
+ constructor() {
+ super();
+
+ this.state = {
+ ignoredUserIds: MatrixClientPeg.get().getIgnoredUsers(),
+ rejectingInvites: false,
+ };
+ }
+
+ _updateBlacklistDevicesFlag = (checked) => {
+ MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
+ };
+
+ _updateAnalytics = (checked) => {
+ checked ? Analytics.enable() : Analytics.disable();
+ };
+
+ _onExportE2eKeysClicked = () => {
+ Modal.createTrackedDialogAsync('Export E2E Keys', '',
+ import('../../../../async-components/views/dialogs/ExportE2eKeysDialog'),
+ {matrixClient: MatrixClientPeg.get()},
+ );
+ };
+
+ _onImportE2eKeysClicked = () => {
+ Modal.createTrackedDialogAsync('Import E2E Keys', '',
+ import('../../../../async-components/views/dialogs/ImportE2eKeysDialog'),
+ {matrixClient: MatrixClientPeg.get()},
+ );
+ };
+
+ _onUserUnignored = async (userId) => {
+ // Don't use this.state to get the ignored user list as it might be
+ // ever so slightly outdated. Instead, prefer to get a fresh list and
+ // update that.
+ const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
+ const index = ignoredUsers.indexOf(userId);
+ if (index !== -1) {
+ ignoredUsers.splice(index, 1);
+ MatrixClientPeg.get().setIgnoredUsers(ignoredUsers);
+ }
+ this.setState({ignoredUsers});
+ };
+
+ _onRejectAllInvitesClicked = (rooms, ev) => {
+ this.setState({
+ rejectingInvites: true,
+ });
+ // reject the invites
+ const promises = rooms.map((room) => {
+ return MatrixClientPeg.get().leave(room.roomId).catch((e) => {
+ // purposefully drop errors to the floor: we'll just have a non-zero number on the UI
+ // after trying to reject all the invites.
+ });
+ });
+ Promise.all(promises).then(() => {
+ this.setState({
+ rejectingInvites: false,
+ });
+ });
+ };
+
+ _renderCurrentDeviceInfo() {
+ const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag');
+
+ const client = MatrixClientPeg.get();
+ const deviceId = client.deviceId;
+ let identityKey = client.getDeviceEd25519Key();
+ if (!identityKey) {
+ identityKey = _t("");
+ } else {
+ identityKey = FormattingUtils.formatCryptoKey(identityKey);
+ }
+
+ let importExportButtons = null;
+ if (client.isCryptoEnabled()) {
+ importExportButtons = (
+
+ {_t("Riot collects anonymous analytics to allow us to improve the application.")}
+
+ {_t("Privacy is important to us, so we don't collect any personal or " +
+ "identifiable data for our analytics.")}
+
+ {_t("Learn more about how we use analytics.")}
+
+
+ );
+ }
+}
diff --git a/src/components/views/verification/VerificationCancelled.js b/src/components/views/verification/VerificationCancelled.js
new file mode 100644
index 0000000000..b21153f2cc
--- /dev/null
+++ b/src/components/views/verification/VerificationCancelled.js
@@ -0,0 +1,40 @@
+/*
+Copyright 2019 Vector Creations Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import sdk from '../../../index';
+import { _t } from '../../../languageHandler';
+
+export default class VerificationCancelled extends React.Component {
+ static propTypes = {
+ onDone: PropTypes.func.isRequired,
+ }
+
+ render() {
+ const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
+ return
+
{_t(
+ "The other party cancelled the verification.",
+ )}
+
+
;
+ }
+}
diff --git a/src/components/views/verification/VerificationComplete.js b/src/components/views/verification/VerificationComplete.js
new file mode 100644
index 0000000000..59f7ff924a
--- /dev/null
+++ b/src/components/views/verification/VerificationComplete.js
@@ -0,0 +1,42 @@
+/*
+Copyright 2019 Vector Creations Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import sdk from '../../../index';
+import { _t } from '../../../languageHandler';
+
+export default class VerificationComplete extends React.Component {
+ static propTypes = {
+ onDone: PropTypes.func.isRequired,
+ }
+
+ render() {
+ const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
+ return
+
{_t("Verified!")}
+
{_t("You've successfully verified this user.")}
+
{_t(
+ "Secure messages with this user are end-to-end encrypted and not able to be " +
+ "read by third parties.",
+ )}
+
+
;
+ }
+}
diff --git a/src/components/views/verification/VerificationShowSas.js b/src/components/views/verification/VerificationShowSas.js
new file mode 100644
index 0000000000..cd4ae59b76
--- /dev/null
+++ b/src/components/views/verification/VerificationShowSas.js
@@ -0,0 +1,65 @@
+/*
+Copyright 2019 Vector Creations Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import sdk from '../../../index';
+import { _t } from '../../../languageHandler';
+
+export default class VerificationShowSas extends React.Component {
+ static propTypes = {
+ onDone: PropTypes.func.isRequired,
+ onCancel: PropTypes.func.isRequired,
+ sas: PropTypes.string.isRequired,
+ }
+
+ constructor() {
+ super();
+ this.state = {
+ sasVerified: false,
+ };
+ }
+
+ _onVerifiedStateChange = (newVal) => {
+ this.setState({sasVerified: newVal});
+ }
+
+ render() {
+ const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
+ const HexVerify = sdk.getComponent('views.elements.HexVerify');
+ return
+
{_t(
+ "Verify this user by confirming the following number appears on their screen.",
+ )}
+
{_t(
+ "For maximum security, we reccommend you do this in person or use another " +
+ "trusted means of communication.",
+ )}
+
+
{_t(
+ "To continue, click on each pair to confirm it's correct.",
+ )}
+
+
;
+ }
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 04328e5151..f04c62cbbb 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -268,6 +268,7 @@
"Backup of encryption keys to server": "Backup of encryption keys to server",
"Render simple counters in room header": "Render simple counters in room header",
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
+ "Two-way device verification using short text": "Two-way device verification using short text",
"Use compact timeline layout": "Use compact timeline layout",
"Show a placeholder for removed messages": "Show a placeholder for removed messages",
"Show join/leave messages (invites/kicks/bans unaffected)": "Show join/leave messages (invites/kicks/bans unaffected)",
@@ -320,6 +321,16 @@
"Incoming call from %(name)s": "Incoming call from %(name)s",
"Decline": "Decline",
"Accept": "Accept",
+ "The other party cancelled the verification.": "The other party cancelled the verification.",
+ "Cancel": "Cancel",
+ "Verified!": "Verified!",
+ "You've successfully verified this user.": "You've successfully verified this user.",
+ "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.",
+ "Got It": "Got It",
+ "Verify this user by confirming the following number appears on their screen.": "Verify this user by confirming the following number appears on their screen.",
+ "For maximum security, we reccommend you do this in person or use another trusted means of communication.": "For maximum security, we reccommend you do this in person or use another trusted means of communication.",
+ "To continue, click on each pair to confirm it's correct.": "To continue, click on each pair to confirm it's correct.",
+ "Continue": "Continue",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains",
"Incorrect verification code": "Incorrect verification code",
"Enter Code": "Enter Code",
@@ -334,7 +345,6 @@
"Passwords can't be empty": "Passwords can't be empty",
"Warning!": "Warning!",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
- "Continue": "Continue",
"Export E2E room keys": "Export E2E room keys",
"Do you want to set an email address?": "Do you want to set an email address?",
"Current password": "Current password",
@@ -439,6 +449,26 @@
"Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!",
"Close Account": "Close Account",
"General": "General",
+ "Legal": "Legal",
+ "For help with using Riot, click here.": "For help with using Riot, click here.",
+ "For help with using Riot, click here or start a chat with our bot using the button below.": "For help with using Riot, click here or start a chat with our bot using the button below.",
+ "Start a chat with Riot Bot": "Start a chat with Riot Bot",
+ "Check for update": "Check for update",
+ "Help & About": "Help & About",
+ "Bug reporting": "Bug reporting",
+ "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.",
+ "Submit debug logs": "Submit debug logs",
+ "Clear Cache and Reload": "Clear Cache and Reload",
+ "FAQ": "FAQ",
+ "Versions": "Versions",
+ "matrix-react-sdk version:": "matrix-react-sdk version:",
+ "riot-web version:": "riot-web version:",
+ "olm version:": "olm version:",
+ "Advanced": "Advanced",
+ "Homeserver is": "Homeserver is",
+ "Identity Server is": "Identity Server is",
+ "Access Token:": "Access Token:",
+ "click to reveal": "click to reveal",
"Lazy loading members not supported": "Lazy loading members not supported",
"Lazy loading is not supported by your current homeserver.": "Lazy loading is not supported by your current homeserver.",
"Labs": "Labs",
@@ -448,8 +478,22 @@
"Composer": "Composer",
"Room list": "Room list",
"Timeline": "Timeline",
- "Advanced": "Advanced",
"Autocomplete delay (ms)": "Autocomplete delay (ms)",
+ "Unignore": "Unignore",
+ "": "",
+ "Import E2E room keys": "Import E2E room keys",
+ "Cryptography": "Cryptography",
+ "Device ID:": "Device ID:",
+ "Device key:": "Device key:",
+ "Ignored users": "Ignored users",
+ "Bulk options": "Bulk options",
+ "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
+ "Key backup": "Key backup",
+ "Security & Privacy": "Security & Privacy",
+ "Devices": "Devices",
+ "Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.",
+ "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.",
+ "Learn more about how we use analytics.": "Learn more about how we use analytics.",
"No media permissions": "No media permissions",
"You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam",
"Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.",
@@ -510,8 +554,6 @@
"Failed to change power level": "Failed to change power level",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
"No devices with registered encryption keys": "No devices with registered encryption keys",
- "Devices": "Devices",
- "Unignore": "Unignore",
"Ignore": "Ignore",
"Jump to read receipt": "Jump to read receipt",
"Mention": "Mention",
@@ -635,11 +677,13 @@
"You are trying to access a room.": "You are trying to access a room.",
"Click here to join the discussion!": "Click here to join the discussion!",
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
- "To view your secure message history and ensure you can view new messages on future devices, set up Secure Message Recovery.": "To view your secure message history and ensure you can view new messages on future devices, set up Secure Message Recovery.",
+ "Set up": "Set up",
+ "Secure Message Recovery has been set up on another device: ": "Secure Message Recovery has been set up on another device: ",
+ "To view your secure message history and ensure you can view new messages on future devices, verify that device now.": "To view your secure message history and ensure you can view new messages on future devices, verify that device now.",
+ "Verify device": "Verify device",
"If you log out or use another device, you'll lose your secure message history. To prevent this, set up Secure Message Recovery.": "If you log out or use another device, you'll lose your secure message history. To prevent this, set up Secure Message Recovery.",
"Secure Message Recovery": "Secure Message Recovery",
"Don't ask again": "Don't ask again",
- "Set up": "Set up",
"To change the room's avatar, you must be a": "To change the room's avatar, you must be a",
"To change the room's name, you must be a": "To change the room's name, you must be a",
"To change the room's main address, you must be a": "To change the room's main address, you must be a",
@@ -706,7 +750,6 @@
"This Room": "This Room",
"All Rooms": "All Rooms",
"Search…": "Search…",
- "Cancel": "Cancel",
"You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
"Add some now": "Add some now",
"Stickerpack": "Stickerpack",
@@ -916,7 +959,6 @@
"Logs sent": "Logs sent",
"Thank you!": "Thank you!",
"Failed to send logs: ": "Failed to send logs: ",
- "Submit debug logs": "Submit debug logs",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.",
"Before submitting logs, you must create a GitHub issue to describe your problem.": "Before submitting logs, you must create a GitHub issue to describe your problem.",
"What GitHub issue are these logs for?": "What GitHub issue are these logs for?",
@@ -961,12 +1003,17 @@
"Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)",
"To continue, please enter your password:": "To continue, please enter your password:",
"password": "password",
+ "Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)",
+ "Verify by comparing a short text string.": "Verify by comparing a short text string.",
+ "For maximum security, we recommend you do this in person or use another trusted means of communication.": "For maximum security, we recommend you do this in person or use another trusted means of communication.",
+ "Begin Verifying": "Begin Verifying",
+ "Waiting for partner to accept...": "Waiting for partner to accept...",
+ "Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...",
+ "Use two-way text verification": "Use two-way text verification",
"To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:",
"Device name": "Device name",
"Device key": "Device key",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.",
- "In future this verification process will be more sophisticated.": "In future this verification process will be more sophisticated.",
- "Verify device": "Verify device",
"I verify that the keys match": "I verify that the keys match",
"Back": "Back",
"Send Custom Event": "Send Custom Event",
@@ -983,6 +1030,10 @@
"Toolbox": "Toolbox",
"Developer Tools": "Developer Tools",
"An error has occurred.": "An error has occurred.",
+ "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
+ "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.",
+ "Waiting for partner to confirm...": "Waiting for partner to confirm...",
+ "Incoming Verification Request": "Incoming Verification Request",
"You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.",
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.",
"Start verification": "Start verification",
@@ -1001,11 +1052,13 @@
"For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.",
"Set a Recovery Method": "Set a Recovery Method",
"I understand, log out without": "I understand, log out without",
- "When signing in again, you can access encrypted chat history by restoring your key backup. You'll need your recovery key.": "When signing in again, you can access encrypted chat history by restoring your key backup. You'll need your recovery key.",
+ "When signing in again, you can access encrypted chat history by restoring your key backup. You'll need your recovery passphrase or, if you didn't set a recovery passphrase, your recovery key (that you downloaded).": "When signing in again, you can access encrypted chat history by restoring your key backup. You'll need your recovery passphrase or, if you didn't set a recovery passphrase, your recovery key (that you downloaded).",
"Thanks for testing the Riot Redesign. If you run into any bugs or visual issues, please let us know on GitHub.": "Thanks for testing the Riot Redesign. If you run into any bugs or visual issues, please let us know on GitHub.",
"To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.",
"Report bugs & give feedback": "Report bugs & give feedback",
"Go back": "Go back",
+ "Roles & Permissions": "Roles & Permissions",
+ "Visit old settings": "Visit old settings",
"Failed to upgrade room": "Failed to upgrade room",
"The room upgrade could not be completed": "The room upgrade could not be completed",
"Upgrade this room to version %(version)s": "Upgrade this room to version %(version)s",
@@ -1056,9 +1109,6 @@
"Room contains unknown devices": "Room contains unknown devices",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
"Unknown devices": "Unknown devices",
- "Security & Privacy": "Security & Privacy",
- "Help & About": "Help & About",
- "Visit old settings": "Visit old settings",
"Unable to load backup status": "Unable to load backup status",
"Unable to restore backup": "Unable to restore backup",
"No backup found!": "No backup found!",
@@ -1142,7 +1192,6 @@
"Sign in": "Sign in",
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?",
"Email address (optional)": "Email address (optional)",
- "You are registering with %(SelectedTeamName)s": "You are registering with %(SelectedTeamName)s",
"Mobile phone number (optional)": "Mobile phone number (optional)",
"Default server": "Default server",
"Custom server": "Custom server",
@@ -1284,31 +1333,17 @@
"Server may be unavailable or overloaded": "Server may be unavailable or overloaded",
"Remove Contact Information?": "Remove Contact Information?",
"Remove %(threePid)s?": "Remove %(threePid)s?",
- "Refer a friend to Riot:": "Refer a friend to Riot:",
"Interface Language": "Interface Language",
"User Interface": "User Interface",
"Autocomplete Delay (ms):": "Autocomplete Delay (ms):",
- "": "",
- "Import E2E room keys": "Import E2E room keys",
"Key Backup": "Key Backup",
- "Cryptography": "Cryptography",
- "Device ID:": "Device ID:",
- "Device key:": "Device key:",
"Ignored Users": "Ignored Users",
"Submit Debug Logs": "Submit Debug Logs",
- "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.",
- "Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.",
- "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.",
- "Learn more about how we use analytics.": "Learn more about how we use analytics.",
"These are experimental features that may break in unexpected ways": "These are experimental features that may break in unexpected ways",
"Use with caution": "Use with caution",
"Deactivate my account": "Deactivate my account",
- "Legal": "Legal",
"Clear Cache": "Clear Cache",
- "Clear Cache and Reload": "Clear Cache and Reload",
"Updates": "Updates",
- "Check for update": "Check for update",
- "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
"Bulk Options": "Bulk Options",
"Desktop specific": "Desktop specific",
"Missing Media Permissions, click here to request.": "Missing Media Permissions, click here to request.",
@@ -1318,13 +1353,6 @@
"Display name": "Display name",
"To return to your account in future you need to set a password": "To return to your account in future you need to set a password",
"Logged in as:": "Logged in as:",
- "Access Token:": "Access Token:",
- "click to reveal": "click to reveal",
- "Homeserver is": "Homeserver is",
- "Identity Server is": "Identity Server is",
- "matrix-react-sdk version:": "matrix-react-sdk version:",
- "riot-web version:": "riot-web version:",
- "olm version:": "olm version:",
"Failed to send email": "Failed to send email",
"The email address linked to your account must be entered.": "The email address linked to your account must be entered.",
"A new password must be entered.": "A new password must be entered.",
diff --git a/src/index.js b/src/index.js
index 8c290fcb64..7d0547d9c9 100644
--- a/src/index.js
+++ b/src/index.js
@@ -15,7 +15,6 @@ limitations under the License.
*/
import Skinner from './Skinner';
-import RtsClient from './RtsClient';
module.exports.loadSkin = function(skinObject) {
Skinner.load(skinObject);
@@ -28,7 +27,3 @@ module.exports.resetSkin = function() {
module.exports.getComponent = function(componentName) {
return Skinner.getComponent(componentName);
};
-
-module.exports.setFetch = function(fetchFunction) {
- RtsClient.setFetch(fetchFunction);
-};
diff --git a/src/resizer/distributors/roomsublist2.js b/src/resizer/distributors/roomsublist2.js
new file mode 100644
index 0000000000..5d291e1516
--- /dev/null
+++ b/src/resizer/distributors/roomsublist2.js
@@ -0,0 +1,305 @@
+/*
+Copyright 2019 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import FixedDistributor from "./fixed";
+
+// const allowWhitespace = true;
+const handleHeight = 1;
+
+function log() {
+}
+
+function clamp(height, min, max) {
+ //log(`clamping ${height} between ${min} and ${max}`);
+ if (height > max) return max;
+ if (height < min) return min;
+ return height;
+}
+
+export class Layout {
+ constructor(applyHeight, initialSizes, collapsedState) {
+ this._applyHeight = applyHeight;
+ this._sections = [];
+ this._collapsedState = Object.assign({}, collapsedState);
+ this._availableHeight = 0;
+ // heights stored by section section id
+ this._sectionHeights = Object.assign({}, initialSizes);
+ // in-progress heights, while dragging. Committed on mouse-up.
+ this._heights = [];
+ }
+
+ setAvailableHeight(newSize) {
+ this._availableHeight = newSize;
+ // needs more work
+ this._applyNewSize();
+ }
+
+ expandSection(id, height) {
+ this._collapsedState[id] = false;
+ this._applyNewSize();
+ this.openHandle(id).setHeight(height).finish();
+ }
+
+ collapseSection(id) {
+ this._collapsedState[id] = true;
+ this._applyNewSize();
+ }
+
+ // [{id, count}]
+ update(sections, availableHeight) {
+ if (Number.isFinite(availableHeight)) {
+ this._availableHeight = availableHeight;
+ }
+ const totalHeight = this._getAvailableHeight();
+ this._sections.forEach((section, i) => {
+ if (!this._sectionHeights.hasOwnProperty(section.id)) {
+ this._sectionHeights[section.id] = clamp(
+ totalHeight / this._sections.length,
+ this._getMinHeight(i),
+ this._getMaxHeight(i),
+ );
+ }
+ });
+ this._sections = sections;
+ this._applyNewSize();
+ }
+
+ openHandle(id) {
+ const index = this._getSectionIndex(id);
+ //log(`openHandle resolved ${id} to ${index}`);
+ return new Handle(this, index, this._sectionHeights[id]);
+ }
+
+ _getAvailableHeight() {
+ const nonCollapsedSectionCount = this._sections.reduce((count, section) => {
+ const collapsed = this._collapsedState[section.id];
+ return count + (collapsed ? 0 : 1);
+ }, 0);
+ return this._availableHeight - ((nonCollapsedSectionCount - 1) * handleHeight);
+ }
+
+ _applyNewSize() {
+ const newHeight = this._getAvailableHeight();
+ const currHeight = this._sections.reduce((sum, section) => {
+ return sum + this._sectionHeights[section.id];
+ }, 0);
+ const offset = newHeight - currHeight;
+ this._heights = this._sections.map((section) => this._sectionHeights[section.id]);
+ const sections = this._sections.map((_, i) => i);
+ this._applyOverflow(-offset, sections, true);
+ this._applyHeights();
+ this._commitHeights();
+ }
+
+ _getSectionIndex(id) {
+ return this._sections.findIndex((s) => s.id === id);
+ }
+
+ _getMaxHeight(i) {
+ const section = this._sections[i];
+ const collapsed = this._collapsedState[section.id];
+
+ if (collapsed) {
+ return this._sectionHeight(0);
+ } else {
+ return 100000;
+ // return this._sectionHeight(section.count);
+ }
+ }
+
+ _sectionHeight(count) {
+ return 36 + (count === 0 ? 0 : 4 + (count * 34));
+ }
+
+ _getMinHeight(i) {
+ const section = this._sections[i];
+ const collapsed = this._collapsedState[section.id];
+ const maxItems = collapsed ? 0 : 1;
+ // log("_getMinHeight", i, section);
+ return this._sectionHeight(Math.min(section.count, maxItems));
+ }
+
+ _applyOverflow(overflow, sections, blend) {
+ //log("applyOverflow", overflow, sections);
+ // take the given overflow amount, and applies it to the given sections.
+ // calls itself recursively until it has distributed all the overflow
+ // or run out of unclamped sections.
+
+ const unclampedSections = [];
+
+ let overflowPerSection = blend ? (overflow / sections.length) : overflow;
+ for (const i of sections) {
+ const newHeight = clamp(
+ this._heights[i] - overflowPerSection,
+ this._getMinHeight(i),
+ this._getMaxHeight(i),
+ );
+ if (newHeight == this._heights[i] - overflowPerSection) {
+ unclampedSections.push(i);
+ }
+ // when section is growing, overflow increases?
+ // 100 -= 200 - 300
+ // 100 -= -100
+ // 200
+ overflow -= this._heights[i] - newHeight;
+ // console.log(`this._heights[${i}] (${this._heights[i]}) - newHeight (${newHeight}) = ${this._heights[i] - newHeight}`);
+ // console.log(`changing ${this._heights[i]} to ${newHeight}`);
+ this._heights[i] = newHeight;
+ // console.log(`for section ${i} overflow is ${overflow}`);
+ if (!blend) {
+ overflowPerSection = overflow;
+ if (Math.abs(overflow) < 1.0) break;
+ }
+ }
+
+ if (Math.abs(overflow) > 1.0 && unclampedSections.length > 0) {
+ // we weren't able to distribute all the overflow so recurse and try again
+ // log("recursing with", overflow, unclampedSections);
+ overflow = this._applyOverflow(overflow, unclampedSections, blend);
+ }
+
+ return overflow;
+ }
+
+ _rebalanceAbove(anchor, overflowAbove) {
+ if (Math.abs(overflowAbove) > 1.0) {
+ // log(`trying to rebalance upstream with ${overflowAbove}`);
+ const sections = [];
+ for (let i = anchor - 1; i >= 0; i--) {
+ sections.push(i);
+ }
+ overflowAbove = this._applyOverflow(overflowAbove, sections);
+ }
+ return overflowAbove;
+ }
+
+ _rebalanceBelow(anchor, overflowBelow) {
+ if (Math.abs(overflowBelow) > 1.0) {
+ // log(`trying to rebalance downstream with ${overflowBelow}`);
+ const sections = [];
+ for (let i = anchor + 1; i < this._sections.length; i++) {
+ sections.push(i);
+ }
+ overflowBelow = this._applyOverflow(overflowBelow, sections);
+ //log(`rebalanced downstream with ${overflowBelow}`);
+ }
+ return overflowBelow;
+ }
+
+ // @param offset the amount the anchor is moved from what is stored in _sectionHeights, positive if downwards
+ // if we're clamped, return the offset we should be clamped at.
+ _relayout(anchor = 0, offset = 0, clamped = false) {
+ this._heights = this._sections.map((section) => this._sectionHeights[section.id]);
+ // are these the amounts the items above/below shrank/grew and need to be relayouted?
+ let overflowAbove;
+ let overflowBelow;
+ const maxHeight = this._getMaxHeight(anchor);
+ const minHeight = this._getMinHeight(anchor);
+ // new height > max ?
+ if (this._heights[anchor] + offset > maxHeight) {
+ // we're pulling downwards and clamped
+ // overflowAbove = minus how much are we above max height?
+ overflowAbove = (maxHeight - this._heights[anchor]) - offset;
+ overflowBelow = offset;
+ // log(`pulling downwards clamped at max: ${overflowAbove} ${overflowBelow}`);
+ } else if (this._heights[anchor] + offset < minHeight) { // new height < min?
+ // we're pulling upwards and clamped
+ // overflowAbove = ??? (offset is negative here, so - offset will add)
+ overflowAbove = (minHeight - this._heights[anchor]) - offset;
+ overflowBelow = offset;
+ // log(`pulling upwards clamped at min: ${overflowAbove} ${overflowBelow}`);
+ } else {
+ overflowAbove = 0;
+ overflowBelow = offset;
+ // log(`resizing the anchor: ${overflowAbove} ${overflowBelow}`);
+ }
+ this._heights[anchor] = clamp(this._heights[anchor] + offset, minHeight, maxHeight);
+
+ // these are reassigned the amount of overflow that could not be rebalanced
+ // meaning we dragged the handle too far and it can't follow the cursor anymore
+ overflowAbove = this._rebalanceAbove(anchor, overflowAbove);
+ overflowBelow = this._rebalanceBelow(anchor, overflowBelow);
+
+ if (!clamped) { // to avoid risk of infinite recursion
+ // clamp to avoid overflowing or underflowing the page
+ if (Math.abs(overflowAbove) > 1.0) {
+ // log(`clamping with overflowAbove ${overflowAbove}`);
+ // here we do the layout again with offset - the amount of space we took too much
+ this._relayout(anchor, offset + overflowAbove, true);
+ return offset + overflowAbove;
+ }
+
+ if (Math.abs(overflowBelow) > 1.0) {
+ // here we do the layout again with offset - the amount of space we took too much
+ // log(`clamping with overflowBelow ${overflowBelow}`);
+ this._relayout(anchor, offset - overflowBelow, true);
+ return offset - overflowBelow;
+ }
+ }
+
+ this._applyHeights();
+ return undefined;
+ }
+
+ _applyHeights() {
+ log("updating layout, heights are now", this._heights);
+ // apply the heights
+ for (let i = 0; i < this._sections.length; i++) {
+ const section = this._sections[i];
+ this._applyHeight(section.id, this._heights[i]);
+ }
+ }
+
+ _commitHeights() {
+ this._sections.forEach((section, i) => {
+ this._sectionHeights[section.id] = this._heights[i];
+ });
+ }
+}
+
+class Handle {
+ constructor(layout, anchor, height) {
+ this._layout = layout;
+ this._anchor = anchor;
+ this._initialHeight = height;
+ }
+
+ setHeight(height) {
+ this._layout._relayout(this._anchor, height - this._initialHeight);
+ return this;
+ }
+
+ finish() {
+ this._layout._commitHeights();
+ return this;
+ }
+}
+
+export class Distributor extends FixedDistributor {
+ constructor(item, cfg) {
+ super(item);
+ const layout = cfg.layout;
+ this._handle = layout.openHandle(item.id);
+ }
+
+ finish() {
+ this._handle.finish();
+ }
+
+ resize(height) {
+ this._handle.setHeight(height);
+ }
+}
diff --git a/src/settings/Settings.js b/src/settings/Settings.js
index fc878333e3..f21e94ea4a 100644
--- a/src/settings/Settings.js
+++ b/src/settings/Settings.js
@@ -125,6 +125,12 @@ export const SETTINGS = {
supportedLevels: LEVELS_FEATURE,
default: false,
},
+ "feature_sas": {
+ isFeature: true,
+ displayName: _td("Two-way device verification using short text"),
+ supportedLevels: LEVELS_FEATURE,
+ default: false,
+ },
"MessageComposerInput.suggestEmoji": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td('Enable Emoji suggestions while typing'),
diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js
index 9e048e5d8e..036a7c04fc 100644
--- a/src/stores/RoomViewStore.js
+++ b/src/stores/RoomViewStore.js
@@ -20,6 +20,7 @@ import MatrixClientPeg from '../MatrixClientPeg';
import sdk from '../index';
import Modal from '../Modal';
import { _t } from '../languageHandler';
+import SettingsStore from "../settings/SettingsStore";
const INITIAL_STATE = {
// Whether we're joining the currently viewed room (see isJoining())
@@ -119,6 +120,16 @@ class RoomViewStore extends Store {
});
break;
case 'open_room_settings':
+ if (SettingsStore.isFeatureEnabled("feature_tabbed_settings")) {
+ const RoomSettingsDialog = sdk.getComponent("dialogs.RoomSettingsDialog");
+ Modal.createTrackedDialog('Room settings', '', RoomSettingsDialog, {}, 'mx_SettingsDialog');
+ } else {
+ this._setState({
+ isEditingSettings: true,
+ });
+ }
+ break;
+ case 'open_old_room_settings':
this._setState({
isEditingSettings: true,
});
diff --git a/test/components/structures/auth/Registration-test.js b/test/components/structures/auth/Registration-test.js
deleted file mode 100644
index 8ce0301128..0000000000
--- a/test/components/structures/auth/Registration-test.js
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
-Copyright 2017 Vector Creations Ltd
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-const jest = require('jest-mock');
-const React = require('react');
-const ReactTestUtils = require('react-addons-test-utils');
-const expect = require('expect');
-
-const testUtils = require('test-utils');
-
-const sdk = require('matrix-react-sdk');
-const Registration = sdk.getComponent('structures.auth.Registration');
-
-let rtsClient;
-let client;
-
-const TEAM_CONFIG = {
- supportEmail: 'support@some.domain',
- teamServerURL: 'http://someteamserver.bla',
-};
-
-const CREDENTIALS = {userId: '@me:here'};
-const MOCK_REG_RESPONSE = {
- user_id: CREDENTIALS.userId,
- device_id: 'mydevice',
- access_token: '2234569864534231',
-};
-
-describe('Registration', function() {
- beforeEach(function() {
- testUtils.beforeEach(this);
- client = testUtils.createTestClient();
- client.credentials = CREDENTIALS;
-
- // Mock an RTS client that supports one team and naively returns team tokens when
- // tracking by mapping email SIDs to team tokens. This is fine because we only
- // want to assert the client behaviour such that a user recognised by the
- // rtsClient (which would normally talk to the RTS server) as a team member is
- // correctly logged in as one (and other such assertions).
- rtsClient = testUtils.createTestRtsClient(
- {
- 'myawesometeam123': {
- name: 'Team Awesome',
- domain: 'team.awesome.net',
- },
- },
- {'someEmailSid1234': 'myawesometeam123'},
- );
- });
-
- it('should track a referral following successful registration of a team member', function(done) {
- const expectedCreds = {
- userId: MOCK_REG_RESPONSE.user_id,
- deviceId: MOCK_REG_RESPONSE.device_id,
- homeserverUrl: client.getHomeserverUrl(),
- identityServerUrl: client.getIdentityServerUrl(),
- accessToken: MOCK_REG_RESPONSE.access_token,
- };
- const onLoggedIn = function(creds, teamToken) {
- expect(creds).toEqual(expectedCreds);
- expect(teamToken).toBe('myawesometeam123');
- done();
- };
-
- const res = ReactTestUtils.renderIntoDocument(
- ,
- );
-
- res._onUIAuthFinished(true, MOCK_REG_RESPONSE, {emailSid: 'someEmailSid1234'});
- });
-
- it('should NOT track a referral following successful registration of a non-team member', function(done) {
- const onLoggedIn = jest.fn(function(creds, teamToken) {
- expect(teamToken).toBeFalsy();
- done();
- });
-
- const res = ReactTestUtils.renderIntoDocument(
- ,
- );
-
- res._onUIAuthFinished(true, MOCK_REG_RESPONSE, {emailSid: 'someOtherEmailSid11'});
- });
-});
diff --git a/test/components/views/auth/RegistrationForm-test.js b/test/components/views/auth/RegistrationForm-test.js
deleted file mode 100644
index 4e7db9a230..0000000000
--- a/test/components/views/auth/RegistrationForm-test.js
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
-Copyright 2017 Vector Creations Ltd
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-const jest = require('jest-mock');
-const React = require('react');
-const ReactTestUtils = require('react-addons-test-utils');
-const expect = require('expect');
-
-const testUtils = require('test-utils');
-
-const sdk = require('matrix-react-sdk');
-const RegistrationForm = sdk.getComponent('views.auth.RegistrationForm');
-
-const TEAM_CONFIG = {
- supportEmail: "support@some.domain",
- teams: [
- { name: "The Team Org.", domain: "team.ac.uk" },
- { name: "The Super Team", domain: "superteam.ac.uk" },
- ],
-};
-
-function doInputEmail(inputEmail, onTeamSelected) {
- const res = ReactTestUtils.renderIntoDocument(
- ,
- );
-
- const teamInput = res.refs.email;
- teamInput.value = inputEmail;
-
- ReactTestUtils.Simulate.change(teamInput);
- ReactTestUtils.Simulate.blur(teamInput);
-
- return res;
-}
-
-function expectTeamSelectedFromEmailInput(inputEmail, expectedTeam) {
- const onTeamSelected = jest.fn();
- doInputEmail(inputEmail, onTeamSelected);
-
- expect(onTeamSelected).toHaveBeenCalledWith(expectedTeam);
-}
-
-function expectSupportFromEmailInput(inputEmail, isSupportShown) {
- const onTeamSelected = jest.fn();
- const res = doInputEmail(inputEmail, onTeamSelected);
-
- expect(res.state.showSupportEmail).toBe(isSupportShown);
-}
-
-describe('RegistrationForm', function() {
- beforeEach(function() {
- testUtils.beforeEach(this);
- });
-
- it('should select a team when a team email is entered', function() {
- expectTeamSelectedFromEmailInput("member@team.ac.uk", TEAM_CONFIG.teams[0]);
- });
-
- it('should not select a team when an unrecognised team email is entered', function() {
- expectTeamSelectedFromEmailInput("member@someunknownteam.ac.uk", null);
- });
-
- it('should show support when an unrecognised team email is entered', function() {
- expectSupportFromEmailInput("member@someunknownteam.ac.uk", true);
- });
-
- it('should NOT show support when an unrecognised non-team email is entered', function() {
- expectSupportFromEmailInput("someone@yahoo.com", false);
- });
-});
diff --git a/test/test-utils.js b/test/test-utils.js
index d5bcd9397a..f4f00effbb 100644
--- a/test/test-utils.js
+++ b/test/test-utils.js
@@ -104,20 +104,6 @@ export function createTestClient() {
};
}
-export function createTestRtsClient(teamMap, sidMap) {
- return {
- getTeamsConfig() {
- return Promise.resolve(Object.keys(teamMap).map((token) => teamMap[token]));
- },
- trackReferral(referrer, emailSid, clientSecret) {
- return Promise.resolve({team_token: sidMap[emailSid]});
- },
- getTeam(teamToken) {
- return Promise.resolve(teamMap[teamToken]);
- },
- };
-}
-
/**
* Create an Event.
* @param {Object} opts Values for the event.