Merge branch 'experimental' into bwindels/rightpanelbelowheader
This commit is contained in:
commit
b4dd739771
79 changed files with 756 additions and 259 deletions
|
@ -7,11 +7,8 @@ src/component-index.js
|
||||||
src/components/structures/BottomLeftMenu.js
|
src/components/structures/BottomLeftMenu.js
|
||||||
src/components/structures/CompatibilityPage.js
|
src/components/structures/CompatibilityPage.js
|
||||||
src/components/structures/CreateRoom.js
|
src/components/structures/CreateRoom.js
|
||||||
src/components/structures/HomePage.js
|
|
||||||
src/components/structures/LeftPanel.js
|
|
||||||
src/components/structures/LoggedInView.js
|
src/components/structures/LoggedInView.js
|
||||||
src/components/structures/login/ForgotPassword.js
|
src/components/structures/login/ForgotPassword.js
|
||||||
src/components/structures/LoginBox.js
|
|
||||||
src/components/structures/MessagePanel.js
|
src/components/structures/MessagePanel.js
|
||||||
src/components/structures/NotificationPanel.js
|
src/components/structures/NotificationPanel.js
|
||||||
src/components/structures/RoomDirectory.js
|
src/components/structures/RoomDirectory.js
|
||||||
|
@ -22,22 +19,17 @@ src/components/structures/SearchBox.js
|
||||||
src/components/structures/TimelinePanel.js
|
src/components/structures/TimelinePanel.js
|
||||||
src/components/structures/UploadBar.js
|
src/components/structures/UploadBar.js
|
||||||
src/components/structures/UserSettings.js
|
src/components/structures/UserSettings.js
|
||||||
src/components/structures/ViewSource.js
|
|
||||||
src/components/views/avatars/BaseAvatar.js
|
src/components/views/avatars/BaseAvatar.js
|
||||||
src/components/views/avatars/MemberAvatar.js
|
src/components/views/avatars/MemberAvatar.js
|
||||||
src/components/views/create_room/RoomAlias.js
|
src/components/views/create_room/RoomAlias.js
|
||||||
src/components/views/dialogs/ChangelogDialog.js
|
|
||||||
src/components/views/dialogs/DeactivateAccountDialog.js
|
src/components/views/dialogs/DeactivateAccountDialog.js
|
||||||
src/components/views/dialogs/SetPasswordDialog.js
|
src/components/views/dialogs/SetPasswordDialog.js
|
||||||
src/components/views/dialogs/UnknownDeviceDialog.js
|
src/components/views/dialogs/UnknownDeviceDialog.js
|
||||||
src/components/views/directory/NetworkDropdown.js
|
src/components/views/directory/NetworkDropdown.js
|
||||||
src/components/views/elements/AddressSelector.js
|
src/components/views/elements/AddressSelector.js
|
||||||
src/components/views/elements/DeviceVerifyButtons.js
|
|
||||||
src/components/views/elements/DirectorySearchBox.js
|
src/components/views/elements/DirectorySearchBox.js
|
||||||
src/components/views/elements/ImageView.js
|
src/components/views/elements/ImageView.js
|
||||||
src/components/views/elements/InlineSpinner.js
|
|
||||||
src/components/views/elements/MemberEventListSummary.js
|
src/components/views/elements/MemberEventListSummary.js
|
||||||
src/components/views/elements/Spinner.js
|
|
||||||
src/components/views/elements/TintableSvg.js
|
src/components/views/elements/TintableSvg.js
|
||||||
src/components/views/elements/UserSelector.js
|
src/components/views/elements/UserSelector.js
|
||||||
src/components/views/globals/MatrixToolbar.js
|
src/components/views/globals/MatrixToolbar.js
|
||||||
|
@ -90,7 +82,6 @@ src/MatrixClientPeg.js
|
||||||
src/Modal.js
|
src/Modal.js
|
||||||
src/notifications/ContentRules.js
|
src/notifications/ContentRules.js
|
||||||
src/notifications/PushRuleVectorState.js
|
src/notifications/PushRuleVectorState.js
|
||||||
src/notifications/StandardActions.js
|
|
||||||
src/notifications/VectorPushRulesDefinitions.js
|
src/notifications/VectorPushRulesDefinitions.js
|
||||||
src/Notifier.js
|
src/Notifier.js
|
||||||
src/PlatformPeg.js
|
src/PlatformPeg.js
|
||||||
|
@ -111,7 +102,6 @@ src/utils/MultiInviter.js
|
||||||
src/utils/Receipt.js
|
src/utils/Receipt.js
|
||||||
src/VectorConferenceHandler.js
|
src/VectorConferenceHandler.js
|
||||||
src/Velociraptor.js
|
src/Velociraptor.js
|
||||||
src/VelocityBounce.js
|
|
||||||
src/WhoIsTyping.js
|
src/WhoIsTyping.js
|
||||||
src/wrappers/withMatrixClient.js
|
src/wrappers/withMatrixClient.js
|
||||||
test/components/structures/login/Registration-test.js
|
test/components/structures/login/Registration-test.js
|
||||||
|
|
44
CHANGELOG.md
44
CHANGELOG.md
|
@ -1,3 +1,47 @@
|
||||||
|
Changes in [0.14.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.2) (2018-10-29)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.2-rc.1...v0.14.2)
|
||||||
|
|
||||||
|
* Fix autoreplacement of ascii emoji
|
||||||
|
[\#2258](https://github.com/matrix-org/matrix-react-sdk/pull/2258)
|
||||||
|
|
||||||
|
Changes in [0.14.2-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.2-rc.1) (2018-10-24)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.1...v0.14.2-rc.1)
|
||||||
|
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#2244](https://github.com/matrix-org/matrix-react-sdk/pull/2244)
|
||||||
|
* Show the group member list again
|
||||||
|
[\#2223](https://github.com/matrix-org/matrix-react-sdk/pull/2223)
|
||||||
|
* lint: make colorScheme camel case
|
||||||
|
[\#2237](https://github.com/matrix-org/matrix-react-sdk/pull/2237)
|
||||||
|
* Change leave room button text, OK -> Leave
|
||||||
|
[\#2236](https://github.com/matrix-org/matrix-react-sdk/pull/2236)
|
||||||
|
* Move all dialog buttons to the right and fix their order
|
||||||
|
[\#2231](https://github.com/matrix-org/matrix-react-sdk/pull/2231)
|
||||||
|
* Add a bit of text to explain the purpose of the RoomPreviewSpinner
|
||||||
|
[\#2225](https://github.com/matrix-org/matrix-react-sdk/pull/2225)
|
||||||
|
* Move the login box from the left sidebar to where the composer is
|
||||||
|
[\#2219](https://github.com/matrix-org/matrix-react-sdk/pull/2219)
|
||||||
|
* Fix an error where React doesn't like value=null on a select
|
||||||
|
[\#2230](https://github.com/matrix-org/matrix-react-sdk/pull/2230)
|
||||||
|
* add missing sticker translation
|
||||||
|
[\#2216](https://github.com/matrix-org/matrix-react-sdk/pull/2216)
|
||||||
|
* Support m.login.terms during registration
|
||||||
|
[\#2221](https://github.com/matrix-org/matrix-react-sdk/pull/2221)
|
||||||
|
* Don't show the invite nag bar when peeking
|
||||||
|
[\#2220](https://github.com/matrix-org/matrix-react-sdk/pull/2220)
|
||||||
|
* Apply the user's tint once the MatrixClientPeg is moderately ready
|
||||||
|
[\#2214](https://github.com/matrix-org/matrix-react-sdk/pull/2214)
|
||||||
|
* Make rageshake use less memory
|
||||||
|
[\#2217](https://github.com/matrix-org/matrix-react-sdk/pull/2217)
|
||||||
|
* Fix autocomplete
|
||||||
|
[\#2212](https://github.com/matrix-org/matrix-react-sdk/pull/2212)
|
||||||
|
* Explain feature states in a lot more detail
|
||||||
|
[\#2211](https://github.com/matrix-org/matrix-react-sdk/pull/2211)
|
||||||
|
* Fix various lint errors
|
||||||
|
[\#2213](https://github.com/matrix-org/matrix-react-sdk/pull/2213)
|
||||||
|
|
||||||
Changes in [0.14.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.1) (2018-10-19)
|
Changes in [0.14.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.1) (2018-10-19)
|
||||||
=====================================================================================================
|
=====================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.0...v0.14.1)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.0...v0.14.1)
|
||||||
|
|
|
@ -199,12 +199,25 @@ module.exports = function (config) {
|
||||||
|
|
||||||
'matrix-react-sdk': path.resolve('test/skinned-sdk.js'),
|
'matrix-react-sdk': path.resolve('test/skinned-sdk.js'),
|
||||||
'sinon': 'sinon/pkg/sinon.js',
|
'sinon': 'sinon/pkg/sinon.js',
|
||||||
|
|
||||||
|
// To make webpack happy
|
||||||
|
// Related: https://github.com/request/request/issues/1529
|
||||||
|
// (there's no mock available for fs, so we fake a mock by using
|
||||||
|
// an in-memory version of fs)
|
||||||
|
"fs": "memfs",
|
||||||
},
|
},
|
||||||
modules: [
|
modules: [
|
||||||
path.resolve('./test'),
|
path.resolve('./test'),
|
||||||
"node_modules"
|
"node_modules"
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
node: {
|
||||||
|
// Because webpack is made of fail
|
||||||
|
// https://github.com/request/request/issues/1529
|
||||||
|
// Note: 'mock' is the new 'empty'
|
||||||
|
net: 'mock',
|
||||||
|
tls: 'mock'
|
||||||
|
},
|
||||||
devtool: 'inline-source-map',
|
devtool: 'inline-source-map',
|
||||||
externals: {
|
externals: {
|
||||||
// Don't try to bundle electron: leave it as a commonjs dependency
|
// Don't try to bundle electron: leave it as a commonjs dependency
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "0.14.1",
|
"version": "0.14.2",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -76,6 +76,7 @@
|
||||||
"lodash": "^4.13.1",
|
"lodash": "^4.13.1",
|
||||||
"lolex": "2.3.2",
|
"lolex": "2.3.2",
|
||||||
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
|
||||||
|
"memfs": "^2.10.1",
|
||||||
"optimist": "^0.6.1",
|
"optimist": "^0.6.1",
|
||||||
"pako": "^1.0.5",
|
"pako": "^1.0.5",
|
||||||
"prop-types": "^15.5.8",
|
"prop-types": "^15.5.8",
|
||||||
|
@ -100,7 +101,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.26.0",
|
"babel-cli": "^6.26.0",
|
||||||
"babel-core": "^6.26.3",
|
"babel-core": "^6.26.3",
|
||||||
"babel-eslint": "^6.1.2",
|
"babel-eslint": "^10.0.1",
|
||||||
"babel-loader": "^7.1.5",
|
"babel-loader": "^7.1.5",
|
||||||
"babel-plugin-add-module-exports": "^0.2.1",
|
"babel-plugin-add-module-exports": "^0.2.1",
|
||||||
"babel-plugin-transform-async-to-bluebird": "^1.1.1",
|
"babel-plugin-transform-async-to-bluebird": "^1.1.1",
|
||||||
|
@ -114,9 +115,9 @@
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-preset-react": "^6.24.1",
|
||||||
"chokidar": "^1.6.1",
|
"chokidar": "^1.6.1",
|
||||||
"concurrently": "^4.0.1",
|
"concurrently": "^4.0.1",
|
||||||
"eslint": "^3.13.1",
|
"eslint": "^5.8.0",
|
||||||
"eslint-config-google": "^0.7.1",
|
"eslint-config-google": "^0.7.1",
|
||||||
"eslint-plugin-babel": "^4.1.2",
|
"eslint-plugin-babel": "^5.2.1",
|
||||||
"eslint-plugin-flowtype": "^2.30.0",
|
"eslint-plugin-flowtype": "^2.30.0",
|
||||||
"eslint-plugin-react": "^7.7.0",
|
"eslint-plugin-react": "^7.7.0",
|
||||||
"estree-walker": "^0.5.0",
|
"estree-walker": "^0.5.0",
|
||||||
|
|
|
@ -167,8 +167,7 @@ textarea {
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 58px;
|
padding: 0 58px 36px;
|
||||||
padding-bottom: 36px;
|
|
||||||
width: 60%;
|
width: 60%;
|
||||||
max-width: 704px;
|
max-width: 704px;
|
||||||
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2);
|
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2);
|
||||||
|
@ -213,14 +212,13 @@ textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog_content {
|
.mx_Dialog_content {
|
||||||
margin: 24px 58px 68px 0;
|
margin: 24px 0 68px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog_buttons {
|
.mx_Dialog_buttons {
|
||||||
padding-right: 58px;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,10 @@ limitations under the License.
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_TagTileContextMenu_item object {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.mx_TagTileContextMenu_item_icon {
|
.mx_TagTileContextMenu_item_icon {
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
|
|
|
@ -14,10 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_DevTools_dialog {
|
|
||||||
padding-right: 58px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_DevTools_content {
|
.mx_DevTools_content {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_ShareDialog {
|
|
||||||
// this is to center the content
|
|
||||||
padding-right: 58px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_ShareDialog hr {
|
.mx_ShareDialog hr {
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
|
|
|
@ -20,9 +20,6 @@ limitations under the License.
|
||||||
// is a pain in the ass. plus might as well make the dialog big given how
|
// is a pain in the ass. plus might as well make the dialog big given how
|
||||||
// important it is.
|
// important it is.
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
// position the gemini scrollbar nicely
|
|
||||||
padding-right: 58px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UnknownDeviceDialog {
|
.mx_UnknownDeviceDialog {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2107 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2107 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
@ -22,7 +22,6 @@ import _clamp from 'lodash/clamp';
|
||||||
type MessageFormat = 'rich' | 'markdown';
|
type MessageFormat = 'rich' | 'markdown';
|
||||||
|
|
||||||
class HistoryItem {
|
class HistoryItem {
|
||||||
|
|
||||||
// We store history items in their native format to ensure history is accurate
|
// We store history items in their native format to ensure history is accurate
|
||||||
// and then convert them if our RTE has subsequently changed format.
|
// and then convert them if our RTE has subsequently changed format.
|
||||||
value: Value;
|
value: Value;
|
||||||
|
|
|
@ -78,7 +78,6 @@ class MemberEntity extends Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserEntity extends Entity {
|
class UserEntity extends Entity {
|
||||||
|
|
||||||
constructor(model, showInviteButton, inviteFn) {
|
constructor(model, showInviteButton, inviteFn) {
|
||||||
super(model);
|
super(model);
|
||||||
this.showInviteButton = Boolean(showInviteButton);
|
this.showInviteButton = Boolean(showInviteButton);
|
||||||
|
|
|
@ -64,7 +64,7 @@ export function containsEmoji(str) {
|
||||||
* because we want to include emoji shortnames in title text
|
* because we want to include emoji shortnames in title text
|
||||||
*/
|
*/
|
||||||
function unicodeToImage(str) {
|
function unicodeToImage(str) {
|
||||||
let replaceWith, unicode, alt, short, fname;
|
let replaceWith; let unicode; let alt; let short; let fname;
|
||||||
const mappedUnicode = emojione.mapUnicodeToShort();
|
const mappedUnicode = emojione.mapUnicodeToShort();
|
||||||
|
|
||||||
str = str.replace(emojione.regUnicode, function(unicodeChar) {
|
str = str.replace(emojione.regUnicode, function(unicodeChar) {
|
||||||
|
|
|
@ -25,7 +25,6 @@ import { _t } from './languageHandler';
|
||||||
* API on the homeserver in question with the new password.
|
* API on the homeserver in question with the new password.
|
||||||
*/
|
*/
|
||||||
class PasswordReset {
|
class PasswordReset {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure the endpoints for password resetting.
|
* Configure the endpoints for password resetting.
|
||||||
* @param {string} homeserverUrl The URL to the HS which has the account to reset.
|
* @param {string} homeserverUrl The URL to the HS which has the account to reset.
|
||||||
|
|
|
@ -23,7 +23,6 @@ const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
|
||||||
const PRESENCE_STATES = ["online", "offline", "unavailable"];
|
const PRESENCE_STATES = ["online", "offline", "unavailable"];
|
||||||
|
|
||||||
class Presence {
|
class Presence {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start listening the user activity to evaluate his presence state.
|
* Start listening the user activity to evaluate his presence state.
|
||||||
* Any state change will be sent to the Home Server.
|
* Any state change will be sent to the Home Server.
|
||||||
|
|
|
@ -32,7 +32,6 @@ export function getDisplayAliasForRoom(room) {
|
||||||
* return the other one. Otherwise, return null.
|
* return the other one. Otherwise, return null.
|
||||||
*/
|
*/
|
||||||
export function getOnlyOtherMember(room, myUserId) {
|
export function getOnlyOtherMember(room, myUserId) {
|
||||||
|
|
||||||
if (room.currentState.getJoinedMemberCount() === 2) {
|
if (room.currentState.getJoinedMemberCount() === 2) {
|
||||||
return room.getJoinedMembers().filter(function(m) {
|
return room.getJoinedMembers().filter(function(m) {
|
||||||
return m.userId !== myUserId;
|
return m.userId !== myUserId;
|
||||||
|
@ -103,7 +102,7 @@ export function guessAndSetDMRoom(room, isDirect) {
|
||||||
let newTarget;
|
let newTarget;
|
||||||
if (isDirect) {
|
if (isDirect) {
|
||||||
const guessedUserId = guessDMRoomTargetId(
|
const guessedUserId = guessDMRoomTargetId(
|
||||||
room, MatrixClientPeg.get().getUserId()
|
room, MatrixClientPeg.get().getUserId(),
|
||||||
);
|
);
|
||||||
newTarget = guessedUserId;
|
newTarget = guessedUserId;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -22,7 +22,6 @@ const SdkConfig = require('./SdkConfig');
|
||||||
const MatrixClientPeg = require('./MatrixClientPeg');
|
const MatrixClientPeg = require('./MatrixClientPeg');
|
||||||
|
|
||||||
class ScalarAuthClient {
|
class ScalarAuthClient {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.scalarToken = null;
|
this.scalarToken = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ const DEFAULTS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
class SdkConfig {
|
class SdkConfig {
|
||||||
|
|
||||||
static get() {
|
static get() {
|
||||||
return global.mxReactSdkConfig || {};
|
return global.mxReactSdkConfig || {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ const CURRENTLY_ACTIVE_THRESHOLD_MS = 2000;
|
||||||
* with the app (but at a much lower frequency than mouse move events)
|
* with the app (but at a much lower frequency than mouse move events)
|
||||||
*/
|
*/
|
||||||
class UserActivity {
|
class UserActivity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start listening to user activity
|
* Start listening to user activity
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,8 +3,8 @@ const Velocity = require('velocity-vector');
|
||||||
// courtesy of https://github.com/julianshapiro/velocity/issues/283
|
// courtesy of https://github.com/julianshapiro/velocity/issues/283
|
||||||
// We only use easeOutBounce (easeInBounce is just sort of nonsensical)
|
// We only use easeOutBounce (easeInBounce is just sort of nonsensical)
|
||||||
function bounce( p ) {
|
function bounce( p ) {
|
||||||
let pow2,
|
let pow2;
|
||||||
bounce = 4;
|
let bounce = 4;
|
||||||
|
|
||||||
while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {
|
while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {
|
||||||
// just sets pow2
|
// just sets pow2
|
||||||
|
|
|
@ -85,7 +85,7 @@ export default class Autocompleter {
|
||||||
provider
|
provider
|
||||||
.getCompletions(query, selection, force)
|
.getCompletions(query, selection, force)
|
||||||
.timeout(PROVIDER_COMPLETION_TIMEOUT)
|
.timeout(PROVIDER_COMPLETION_TIMEOUT)
|
||||||
.reflect()
|
.reflect(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ import { Block } from 'slate';
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class PlainWithPillsSerializer {
|
class PlainWithPillsSerializer {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @param {String} options.pillFormat - either 'md', 'plain', 'id'
|
* @param {String} options.pillFormat - either 'md', 'plain', 'id'
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -145,7 +145,7 @@ module.exports = React.createClass({
|
||||||
// Get the label/tooltip to show
|
// Get the label/tooltip to show
|
||||||
getLabel: function(label, show) {
|
getLabel: function(label, show) {
|
||||||
if (show) {
|
if (show) {
|
||||||
var RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
||||||
return <RoomTooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
|
return <RoomTooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,18 +16,18 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
const React = require('react');
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'CompatibilityPage',
|
displayName: 'CompatibilityPage',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onAccept: React.PropTypes.func
|
onAccept: React.PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
onAccept: function() {} // NOP
|
onAccept: function() {}, // NOP
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_CompatibilityPage">
|
<div className="mx_CompatibilityPage">
|
||||||
<div className="mx_CompatibilityPage_box">
|
<div className="mx_CompatibilityPage_box">
|
||||||
|
@ -69,5 +68,5 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,15 +52,14 @@ class HomePage extends React.Component {
|
||||||
|
|
||||||
if (this.props.teamToken && this.props.teamServerUrl) {
|
if (this.props.teamToken && this.props.teamServerUrl) {
|
||||||
this.setState({
|
this.setState({
|
||||||
iframeSrc: `${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html`
|
iframeSrc: `${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html`,
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// we use request() to inline the homepage into the react component
|
// we use request() to inline the homepage into the react component
|
||||||
// so that it can inherit CSS and theming easily rather than mess around
|
// so that it can inherit CSS and theming easily rather than mess around
|
||||||
// with iframes and trying to synchronise document.stylesheets.
|
// with iframes and trying to synchronise document.stylesheets.
|
||||||
|
|
||||||
let src = this.props.homePageUrl || 'home.html';
|
const src = this.props.homePageUrl || 'home.html';
|
||||||
|
|
||||||
request(
|
request(
|
||||||
{ method: "GET", url: src },
|
{ method: "GET", url: src },
|
||||||
|
@ -77,7 +76,7 @@ class HomePage extends React.Component {
|
||||||
|
|
||||||
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
|
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
|
||||||
this.setState({ page: body });
|
this.setState({ page: body });
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,8 +92,7 @@ class HomePage extends React.Component {
|
||||||
<iframe src={ this.state.iframeSrc } />
|
<iframe src={ this.state.iframeSrc } />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
||||||
return (
|
return (
|
||||||
<GeminiScrollbarWrapper autoshow={true} className="mx_HomePage">
|
<GeminiScrollbarWrapper autoshow={true} className="mx_HomePage">
|
||||||
|
|
|
@ -28,7 +28,7 @@ import VectorConferenceHandler from '../../VectorConferenceHandler';
|
||||||
import SettingsStore from '../../settings/SettingsStore';
|
import SettingsStore from '../../settings/SettingsStore';
|
||||||
|
|
||||||
|
|
||||||
var LeftPanel = React.createClass({
|
const LeftPanel = React.createClass({
|
||||||
displayName: 'LeftPanel',
|
displayName: 'LeftPanel',
|
||||||
|
|
||||||
// NB. If you add props, don't forget to update
|
// NB. If you add props, don't forget to update
|
||||||
|
@ -214,7 +214,7 @@ var LeftPanel = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
// <BottomLeftMenu collapsed={this.props.collapsed}/>
|
// <BottomLeftMenu collapsed={this.props.collapsed}/>
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = LeftPanel;
|
module.exports = LeftPanel;
|
||||||
|
|
|
@ -65,6 +65,9 @@ const LoggedInView = React.createClass({
|
||||||
|
|
||||||
teamToken: PropTypes.string,
|
teamToken: PropTypes.string,
|
||||||
|
|
||||||
|
// Used by the RoomView to handle joining rooms
|
||||||
|
viaServers: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
|
||||||
// and lots and lots of other stuff.
|
// and lots and lots of other stuff.
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -433,6 +436,7 @@ const LoggedInView = React.createClass({
|
||||||
onRegistered={this.props.onRegistered}
|
onRegistered={this.props.onRegistered}
|
||||||
thirdPartyInvite={this.props.thirdPartyInvite}
|
thirdPartyInvite={this.props.thirdPartyInvite}
|
||||||
oobData={this.props.roomOobData}
|
oobData={this.props.roomOobData}
|
||||||
|
viaServers={this.props.viaServers}
|
||||||
eventPixelOffset={this.props.initialEventPixelOffset}
|
eventPixelOffset={this.props.initialEventPixelOffset}
|
||||||
key={this.props.currentRoomId || 'roomview'}
|
key={this.props.currentRoomId || 'roomview'}
|
||||||
disabled={this.props.middleDisabled}
|
disabled={this.props.middleDisabled}
|
||||||
|
|
|
@ -53,5 +53,5 @@ module.exports = React.createClass({
|
||||||
{ loginButton }
|
{ loginButton }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -840,6 +840,7 @@ export default React.createClass({
|
||||||
page_type: PageTypes.RoomView,
|
page_type: PageTypes.RoomView,
|
||||||
thirdPartyInvite: roomInfo.third_party_invite,
|
thirdPartyInvite: roomInfo.third_party_invite,
|
||||||
roomOobData: roomInfo.oob_data,
|
roomOobData: roomInfo.oob_data,
|
||||||
|
viaServers: roomInfo.via_servers,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (roomInfo.room_alias) {
|
if (roomInfo.room_alias) {
|
||||||
|
@ -1489,9 +1490,21 @@ export default React.createClass({
|
||||||
inviterName: params.inviter_name,
|
inviterName: params.inviter_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// on our URLs there might be a ?via=matrix.org or similar to help
|
||||||
|
// joins to the room succeed. We'll pass these through as an array
|
||||||
|
// to other levels. If there's just one ?via= then params.via is a
|
||||||
|
// single string. If someone does something like ?via=one.com&via=two.com
|
||||||
|
// then params.via is an array of strings.
|
||||||
|
let via = [];
|
||||||
|
if (params.via) {
|
||||||
|
if (typeof(params.via) === 'string') via = [params.via];
|
||||||
|
else via = params.via;
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
event_id: eventId,
|
event_id: eventId,
|
||||||
|
via_servers: via,
|
||||||
// If an event ID is given in the URL hash, notify RoomViewStore to mark
|
// If an event ID is given in the URL hash, notify RoomViewStore to mark
|
||||||
// it as highlighted, which will propagate to RoomView and highlight the
|
// it as highlighted, which will propagate to RoomView and highlight the
|
||||||
// associated EventTile.
|
// associated EventTile.
|
||||||
|
|
|
@ -16,18 +16,18 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
var MatrixClientPeg = require('../../MatrixClientPeg');
|
const MatrixClientPeg = require('../../MatrixClientPeg');
|
||||||
var ContentRepo = require("matrix-js-sdk").ContentRepo;
|
const ContentRepo = require("matrix-js-sdk").ContentRepo;
|
||||||
var Modal = require('../../Modal');
|
const Modal = require('../../Modal');
|
||||||
var sdk = require('../../index');
|
const sdk = require('../../index');
|
||||||
var dis = require('../../dispatcher');
|
const dis = require('../../dispatcher');
|
||||||
|
|
||||||
var linkify = require('linkifyjs');
|
const linkify = require('linkifyjs');
|
||||||
var linkifyString = require('linkifyjs/string');
|
const linkifyString = require('linkifyjs/string');
|
||||||
var linkifyMatrix = require('../../linkify-matrix');
|
const linkifyMatrix = require('../../linkify-matrix');
|
||||||
var sanitizeHtml = require('sanitize-html');
|
const sanitizeHtml = require('sanitize-html');
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
@ -46,7 +46,7 @@ module.exports = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
config: {},
|
config: {},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -58,7 +58,7 @@ module.exports = React.createClass({
|
||||||
includeAll: false,
|
includeAll: false,
|
||||||
roomServer: null,
|
roomServer: null,
|
||||||
filterString: null,
|
filterString: null,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -139,8 +139,7 @@ module.exports = React.createClass({
|
||||||
if (
|
if (
|
||||||
my_filter_string != this.state.filterString ||
|
my_filter_string != this.state.filterString ||
|
||||||
my_server != this.state.roomServer ||
|
my_server != this.state.roomServer ||
|
||||||
my_next_batch != this.nextBatch)
|
my_next_batch != this.nextBatch) {
|
||||||
{
|
|
||||||
// if the filter or server has changed since this request was sent,
|
// if the filter or server has changed since this request was sent,
|
||||||
// throw away the result (don't even clear the busy flag
|
// throw away the result (don't even clear the busy flag
|
||||||
// since we must still have a request in flight)
|
// since we must still have a request in flight)
|
||||||
|
@ -163,8 +162,7 @@ module.exports = React.createClass({
|
||||||
if (
|
if (
|
||||||
my_filter_string != this.state.filterString ||
|
my_filter_string != this.state.filterString ||
|
||||||
my_server != this.state.roomServer ||
|
my_server != this.state.roomServer ||
|
||||||
my_next_batch != this.nextBatch)
|
my_next_batch != this.nextBatch) {
|
||||||
{
|
|
||||||
// as above: we don't care about errors for old
|
// as above: we don't care about errors for old
|
||||||
// requests either
|
// requests either
|
||||||
return;
|
return;
|
||||||
|
@ -177,10 +175,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
|
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog('Failed to get public room list', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to get public room list', '', ErrorDialog, {
|
||||||
title: _t('Failed to get public room list'),
|
title: _t('Failed to get public room list'),
|
||||||
description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded'))
|
description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded')),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -193,13 +191,13 @@ module.exports = React.createClass({
|
||||||
* this needs SPEC-417.
|
* this needs SPEC-417.
|
||||||
*/
|
*/
|
||||||
removeFromDirectory: function(room) {
|
removeFromDirectory: function(room) {
|
||||||
var alias = get_display_alias_for_room(room);
|
const alias = get_display_alias_for_room(room);
|
||||||
var name = room.name || alias || _t('Unnamed room');
|
const name = room.name || alias || _t('Unnamed room');
|
||||||
|
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
|
||||||
var desc;
|
let desc;
|
||||||
if (alias) {
|
if (alias) {
|
||||||
desc = _t('Delete the room alias %(alias)s and remove %(name)s from the directory?', {alias: alias, name: name});
|
desc = _t('Delete the room alias %(alias)s and remove %(name)s from the directory?', {alias: alias, name: name});
|
||||||
} else {
|
} else {
|
||||||
|
@ -212,9 +210,9 @@ module.exports = React.createClass({
|
||||||
onFinished: (should_delete) => {
|
onFinished: (should_delete) => {
|
||||||
if (!should_delete) return;
|
if (!should_delete) return;
|
||||||
|
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
var modal = Modal.createDialog(Loader);
|
const modal = Modal.createDialog(Loader);
|
||||||
var step = _t('remove %(name)s from the directory.', {name: name});
|
let step = _t('remove %(name)s from the directory.', {name: name});
|
||||||
|
|
||||||
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
|
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
|
||||||
if (!alias) return;
|
if (!alias) return;
|
||||||
|
@ -229,10 +227,10 @@ module.exports = React.createClass({
|
||||||
console.error("Failed to " + step + ": " + err);
|
console.error("Failed to " + step + ": " + err);
|
||||||
Modal.createTrackedDialog('Remove from Directory Error', '', ErrorDialog, {
|
Modal.createTrackedDialog('Remove from Directory Error', '', ErrorDialog, {
|
||||||
title: _t('Error'),
|
title: _t('Error'),
|
||||||
description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded'))
|
description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded')),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -347,7 +345,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
showRoom: function(room, room_alias) {
|
showRoom: function(room, room_alias) {
|
||||||
var payload = {action: 'view_room'};
|
const payload = {action: 'view_room'};
|
||||||
if (room) {
|
if (room) {
|
||||||
// Don't let the user view a room they won't be able to either
|
// Don't let the user view a room they won't be able to either
|
||||||
// peek or join: fail earlier so they don't have to click back
|
// peek or join: fail earlier so they don't have to click back
|
||||||
|
@ -383,16 +381,16 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getRows: function() {
|
getRows: function() {
|
||||||
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
|
|
||||||
if (!this.state.publicRooms) return [];
|
if (!this.state.publicRooms) return [];
|
||||||
|
|
||||||
var rooms = this.state.publicRooms;
|
const rooms = this.state.publicRooms;
|
||||||
var rows = [];
|
const rows = [];
|
||||||
var self = this;
|
const self = this;
|
||||||
var guestRead, guestJoin, perms;
|
let guestRead; let guestJoin; let perms;
|
||||||
for (var i = 0; i < rooms.length; i++) {
|
for (let i = 0; i < rooms.length; i++) {
|
||||||
var name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room');
|
const name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room');
|
||||||
guestRead = null;
|
guestRead = null;
|
||||||
guestJoin = null;
|
guestJoin = null;
|
||||||
|
|
||||||
|
@ -412,7 +410,7 @@ module.exports = React.createClass({
|
||||||
perms = <div className="mx_RoomDirectory_perms">{guestRead}{guestJoin}</div>;
|
perms = <div className="mx_RoomDirectory_perms">{guestRead}{guestJoin}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
var topic = rooms[i].topic || '';
|
let topic = rooms[i].topic || '';
|
||||||
topic = linkifyString(sanitizeHtml(topic));
|
topic = linkifyString(sanitizeHtml(topic));
|
||||||
|
|
||||||
rows.push(
|
rows.push(
|
||||||
|
@ -432,14 +430,14 @@ module.exports = React.createClass({
|
||||||
<div className="mx_RoomDirectory_name">{ name }</div>
|
<div className="mx_RoomDirectory_name">{ name }</div>
|
||||||
{ perms }
|
{ perms }
|
||||||
<div className="mx_RoomDirectory_topic"
|
<div className="mx_RoomDirectory_topic"
|
||||||
onClick={ function(e) { e.stopPropagation() } }
|
onClick={ function(e) { e.stopPropagation(); } }
|
||||||
dangerouslySetInnerHTML={{ __html: topic }} />
|
dangerouslySetInnerHTML={{ __html: topic }} />
|
||||||
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(rooms[i]) }</div>
|
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(rooms[i]) }</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="mx_RoomDirectory_roomMemberCount">
|
<td className="mx_RoomDirectory_roomMemberCount">
|
||||||
{ rooms[i].num_joined_members }
|
{ rooms[i].num_joined_members }
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
|
@ -577,7 +575,7 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
|
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
|
||||||
|
|
|
@ -37,7 +37,7 @@ function getUnsentMessages(room) {
|
||||||
return room.getPendingEvents().filter(function(ev) {
|
return room.getPendingEvents().filter(function(ev) {
|
||||||
return ev.status === Matrix.EventStatus.NOT_SENT;
|
return ev.status === Matrix.EventStatus.NOT_SENT;
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomStatusBar',
|
displayName: 'RoomStatusBar',
|
||||||
|
@ -303,7 +303,7 @@ module.exports = React.createClass({
|
||||||
const errorIsMauError = Boolean(
|
const errorIsMauError = Boolean(
|
||||||
this.state.syncStateData &&
|
this.state.syncStateData &&
|
||||||
this.state.syncStateData.error &&
|
this.state.syncStateData.error &&
|
||||||
this.state.syncStateData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED'
|
this.state.syncStateData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED',
|
||||||
);
|
);
|
||||||
return this.state.syncState === "ERROR" && !errorIsMauError;
|
return this.state.syncState === "ERROR" && !errorIsMauError;
|
||||||
},
|
},
|
||||||
|
|
|
@ -90,6 +90,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// is the RightPanel collapsed?
|
// is the RightPanel collapsed?
|
||||||
collapsedRhs: PropTypes.bool,
|
collapsedRhs: PropTypes.bool,
|
||||||
|
|
||||||
|
// Servers the RoomView can use to try and assist joins
|
||||||
|
viaServers: PropTypes.arrayOf(PropTypes.string),
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -835,7 +838,7 @@ module.exports = React.createClass({
|
||||||
action: 'do_after_sync_prepared',
|
action: 'do_after_sync_prepared',
|
||||||
deferred_action: {
|
deferred_action: {
|
||||||
action: 'join_room',
|
action: 'join_room',
|
||||||
opts: { inviteSignUrl: signUrl },
|
opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -877,7 +880,7 @@ module.exports = React.createClass({
|
||||||
this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'join_room',
|
action: 'join_room',
|
||||||
opts: { inviteSignUrl: signUrl },
|
opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
|
||||||
});
|
});
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
|
@ -1676,7 +1679,7 @@ module.exports = React.createClass({
|
||||||
</AuxPanel>
|
</AuxPanel>
|
||||||
);
|
);
|
||||||
|
|
||||||
let messageComposer, searchInfo;
|
let messageComposer; let searchInfo;
|
||||||
const canSpeak = (
|
const canSpeak = (
|
||||||
// joined and not showing search results
|
// joined and not showing search results
|
||||||
myMembership === 'join' && !this.state.searchResults
|
myMembership === 'join' && !this.state.searchResults
|
||||||
|
@ -1709,7 +1712,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inCall) {
|
if (inCall) {
|
||||||
let zoomButton, voiceMuteButton, videoMuteButton;
|
let zoomButton; let voiceMuteButton; let videoMuteButton;
|
||||||
|
|
||||||
if (call.type === "video") {
|
if (call.type === "video") {
|
||||||
zoomButton = (
|
zoomButton = (
|
||||||
|
|
|
@ -72,7 +72,7 @@ module.exports = React.createClass({
|
||||||
function() {
|
function() {
|
||||||
this.props.onSearch(this.refs.search.value);
|
this.props.onSearch(this.refs.search.value);
|
||||||
},
|
},
|
||||||
100
|
100,
|
||||||
),
|
),
|
||||||
|
|
||||||
onToggleCollapse: function(show) {
|
onToggleCollapse: function(show) {
|
||||||
|
@ -80,8 +80,7 @@ module.exports = React.createClass({
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'show_left_panel',
|
action: 'show_left_panel',
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'hide_left_panel',
|
action: 'hide_left_panel',
|
||||||
});
|
});
|
||||||
|
@ -103,25 +102,24 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var TintableSvg = sdk.getComponent('elements.TintableSvg');
|
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||||
|
|
||||||
var collapseTabIndex = this.refs.search && this.refs.search.value !== "" ? "-1" : "0";
|
const collapseTabIndex = this.refs.search && this.refs.search.value !== "" ? "-1" : "0";
|
||||||
|
|
||||||
var toggleCollapse;
|
let toggleCollapse;
|
||||||
if (this.props.collapsed) {
|
if (this.props.collapsed) {
|
||||||
toggleCollapse =
|
toggleCollapse =
|
||||||
<AccessibleButton className="mx_SearchBox_maximise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, true) }>
|
<AccessibleButton className="mx_SearchBox_maximise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, true) }>
|
||||||
<TintableSvg src="img/maximise.svg" width="10" height="16" alt={ _t("Expand panel") } />
|
<TintableSvg src="img/maximise.svg" width="10" height="16" alt={ _t("Expand panel") } />
|
||||||
</AccessibleButton>
|
</AccessibleButton>;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
toggleCollapse =
|
toggleCollapse =
|
||||||
<AccessibleButton className="mx_SearchBox_minimise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, false) }>
|
<AccessibleButton className="mx_SearchBox_minimise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, false) }>
|
||||||
<TintableSvg src="img/minimise.svg" width="10" height="16" alt={ _t("Collapse panel") } />
|
<TintableSvg src="img/minimise.svg" width="10" height="16" alt={ _t("Collapse panel") } />
|
||||||
</AccessibleButton>
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
var searchControls;
|
let searchControls;
|
||||||
if (!this.props.collapsed) {
|
if (!this.props.collapsed) {
|
||||||
searchControls = [
|
searchControls = [
|
||||||
this.state.searchTerm.length > 0 ?
|
this.state.searchTerm.length > 0 ?
|
||||||
|
@ -148,16 +146,16 @@ module.exports = React.createClass({
|
||||||
onChange={ this.onChange }
|
onChange={ this.onChange }
|
||||||
onKeyDown={ this._onKeyDown }
|
onKeyDown={ this._onKeyDown }
|
||||||
placeholder={ _t('Filter room names') }
|
placeholder={ _t('Filter room names') }
|
||||||
/>
|
/>,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
var self = this;
|
const self = this;
|
||||||
return (
|
return (
|
||||||
<div className="mx_SearchBox">
|
<div className="mx_SearchBox">
|
||||||
{ searchControls }
|
{ searchControls }
|
||||||
{ toggleCollapse }
|
{ toggleCollapse }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1299,7 +1299,7 @@ module.exports = React.createClass({
|
||||||
// If the olmVersion is not defined then either crypto is disabled, or
|
// If the olmVersion is not defined then either crypto is disabled, or
|
||||||
// we are using a version old version of olm. We assume the former.
|
// we are using a version old version of olm. We assume the former.
|
||||||
let olmVersionString = "<not-enabled>";
|
let olmVersionString = "<not-enabled>";
|
||||||
if (olmVersion !== undefined) {
|
if (olmVersion) {
|
||||||
olmVersionString = `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`;
|
olmVersionString = `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,5 +53,5 @@ module.exports = React.createClass({
|
||||||
</SyntaxHighlight>
|
</SyntaxHighlight>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -79,7 +79,7 @@ module.exports = React.createClass({
|
||||||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||||
|
|
||||||
let {member, fallbackUserId, onClick, viewUserOnClick, ...otherProps} = this.props;
|
let {member, fallbackUserId, onClick, viewUserOnClick, ...otherProps} = this.props;
|
||||||
let userId = member ? member.userId : fallbackUserId;
|
const userId = member ? member.userId : fallbackUserId;
|
||||||
|
|
||||||
if (viewUserOnClick) {
|
if (viewUserOnClick) {
|
||||||
onClick = () => {
|
onClick = () => {
|
||||||
|
|
|
@ -66,7 +66,7 @@ export default class ChangelogDialog extends React.Component {
|
||||||
{this.state[repo].map(this._elementsForCommit)}
|
{this.state[repo].map(this._elementsForCommit)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
|
@ -83,7 +83,7 @@ export default class ChangelogDialog extends React.Component {
|
||||||
button={_t("Update")}
|
button={_t("Update")}
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ import Unread from '../../../Unread';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default class ChatCreateOrReuseDialog extends React.Component {
|
export default class ChatCreateOrReuseDialog extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.onFinished = this.onFinished.bind(this);
|
this.onFinished = this.onFinished.bind(this);
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default React.createClass({
|
||||||
let error = null;
|
let error = null;
|
||||||
if (!this.state.groupId) {
|
if (!this.state.groupId) {
|
||||||
error = _t("Community IDs cannot be empty.");
|
error = _t("Community IDs cannot be empty.");
|
||||||
} else if (!/^[a-z0-9=_\-\.\/]*$/.test(this.state.groupId)) {
|
} else if (!/^[a-z0-9=_\-./]*$/.test(this.state.groupId)) {
|
||||||
error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
|
error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -625,7 +625,7 @@ export default class DevtoolsDialog extends React.Component {
|
||||||
let body;
|
let body;
|
||||||
|
|
||||||
if (this.state.mode) {
|
if (this.state.mode) {
|
||||||
body = <div className="mx_DevTools_dialog">
|
body = <div>
|
||||||
<div className="mx_DevTools_label_left">{ this.state.mode.getLabel() }</div>
|
<div className="mx_DevTools_label_left">{ this.state.mode.getLabel() }</div>
|
||||||
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
||||||
<div className="mx_DevTools_label_bottom" />
|
<div className="mx_DevTools_label_bottom" />
|
||||||
|
@ -634,7 +634,7 @@ export default class DevtoolsDialog extends React.Component {
|
||||||
} else {
|
} else {
|
||||||
const classes = "mx_DevTools_RoomStateExplorer_button";
|
const classes = "mx_DevTools_RoomStateExplorer_button";
|
||||||
body = <div>
|
body = <div>
|
||||||
<div className="mx_DevTools_dialog">
|
<div>
|
||||||
<div className="mx_DevTools_label_left">{ _t('Toolbox') }</div>
|
<div className="mx_DevTools_label_left">{ _t('Toolbox') }</div>
|
||||||
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
||||||
<div className="mx_DevTools_label_bottom" />
|
<div className="mx_DevTools_label_bottom" />
|
||||||
|
|
|
@ -153,8 +153,8 @@ export default class NetworkDropdown extends React.Component {
|
||||||
|
|
||||||
const sortedInstances = this.props.protocols[proto].instances;
|
const sortedInstances = this.props.protocols[proto].instances;
|
||||||
sortedInstances.sort(function(x, y) {
|
sortedInstances.sort(function(x, y) {
|
||||||
const a = x.desc
|
const a = x.desc;
|
||||||
const b = y.desc
|
const b = y.desc;
|
||||||
if (a < b) {
|
if (a < b) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (a > b) {
|
} else if (a > b) {
|
||||||
|
@ -208,7 +208,7 @@ export default class NetworkDropdown extends React.Component {
|
||||||
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
|
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
|
||||||
{icon}
|
{icon}
|
||||||
<span className="mx_NetworkDropdown_menu_network">{name}</span>
|
<span className="mx_NetworkDropdown_menu_network">{name}</span>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -223,11 +223,11 @@ export default class NetworkDropdown extends React.Component {
|
||||||
current_value = <input type="text" className="mx_NetworkDropdown_networkoption"
|
current_value = <input type="text" className="mx_NetworkDropdown_networkoption"
|
||||||
ref={this.collectInputTextBox} onKeyUp={this.onInputKeyUp}
|
ref={this.collectInputTextBox} onKeyUp={this.onInputKeyUp}
|
||||||
placeholder="matrix.org" // 'matrix.org' as an example of an HS name
|
placeholder="matrix.org" // 'matrix.org' as an example of an HS name
|
||||||
/>
|
/>;
|
||||||
} else {
|
} else {
|
||||||
const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
|
const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
|
||||||
current_value = this._makeMenuOption(
|
current_value = this._makeMenuOption(
|
||||||
this.state.selectedServer, instance, this.state.includeAll, false
|
this.state.selectedServer, instance, this.state.includeAll, false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -318,6 +318,19 @@ export default class AppTile extends React.Component {
|
||||||
}
|
}
|
||||||
this.setState({deleting: true});
|
this.setState({deleting: true});
|
||||||
|
|
||||||
|
// HACK: This is a really dirty way to ensure that Jitsi cleans up
|
||||||
|
// its hold on the webcam. Without this, the widget holds a media
|
||||||
|
// stream open, even after death. See https://github.com/vector-im/riot-web/issues/7351
|
||||||
|
if (this.refs.appFrame) {
|
||||||
|
// In practice we could just do `+= ''` to trick the browser
|
||||||
|
// into thinking the URL changed, however I can foresee this
|
||||||
|
// being optimized out by a browser. Instead, we'll just point
|
||||||
|
// the iframe at a page that is reasonably safe to use in the
|
||||||
|
// event the iframe doesn't wink away.
|
||||||
|
// This is relative to where the Riot instance is located.
|
||||||
|
this.refs.appFrame.src = 'about:blank';
|
||||||
|
}
|
||||||
|
|
||||||
WidgetUtils.setRoomWidget(
|
WidgetUtils.setRoomWidget(
|
||||||
this.props.room.roomId,
|
this.props.room.roomId,
|
||||||
this.props.id,
|
this.props.id,
|
||||||
|
|
|
@ -78,7 +78,7 @@ export default React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
let blacklistButton = null, verifyButton = null;
|
let blacklistButton = null; let verifyButton = null;
|
||||||
|
|
||||||
if (this.state.device.isBlocked()) {
|
if (this.state.device.isBlocked()) {
|
||||||
blacklistButton = (
|
blacklistButton = (
|
||||||
|
|
|
@ -122,7 +122,6 @@ export default class EditableTextContainer extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableTextContainer.propTypes = {
|
EditableTextContainer.propTypes = {
|
||||||
|
|
|
@ -16,13 +16,13 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
|
||||||
import {formatDate} from '../../../DateUtils';
|
import {formatDate} from '../../../DateUtils';
|
||||||
var filesize = require('filesize');
|
const filesize = require('filesize');
|
||||||
var AccessibleButton = require('../../../components/views/elements/AccessibleButton');
|
const AccessibleButton = require('../../../components/views/elements/AccessibleButton');
|
||||||
const Modal = require('../../../Modal');
|
const Modal = require('../../../Modal');
|
||||||
const sdk = require('../../../index');
|
const sdk = require('../../../index');
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
@ -69,24 +69,24 @@ module.exports = React.createClass({
|
||||||
Modal.createTrackedDialog('Confirm Redact Dialog', 'Image View', ConfirmRedactDialog, {
|
Modal.createTrackedDialog('Confirm Redact Dialog', 'Image View', ConfirmRedactDialog, {
|
||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
var self = this;
|
const self = this;
|
||||||
MatrixClientPeg.get().redactEvent(
|
MatrixClientPeg.get().redactEvent(
|
||||||
this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()
|
this.props.mxEvent.getRoomId(), this.props.mxEvent.getId(),
|
||||||
).catch(function(e) {
|
).catch(function(e) {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
// display error message stating you couldn't delete this.
|
// display error message stating you couldn't delete this.
|
||||||
var code = e.errcode || e.statusCode;
|
const code = e.errcode || e.statusCode;
|
||||||
Modal.createTrackedDialog('You cannot delete this image.', '', ErrorDialog, {
|
Modal.createTrackedDialog('You cannot delete this image.', '', ErrorDialog, {
|
||||||
title: _t('Error'),
|
title: _t('Error'),
|
||||||
description: _t('You cannot delete this image. (%(code)s)', {code: code})
|
description: _t('You cannot delete this image. (%(code)s)', {code: code}),
|
||||||
});
|
});
|
||||||
}).done();
|
}).done();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getName: function() {
|
getName: function() {
|
||||||
var name = this.props.name;
|
let name = this.props.name;
|
||||||
if (name && this.props.link) {
|
if (name && this.props.link) {
|
||||||
name = <a href={ this.props.link } target="_blank" rel="noopener">{ name }</a>;
|
name = <a href={ this.props.link } target="_blank" rel="noopener">{ name }</a>;
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// In theory max-width: 80%, max-height: 80% on the CSS should work
|
// In theory max-width: 80%, max-height: 80% on the CSS should work
|
||||||
// but in practice, it doesn't, so do it manually:
|
// but in practice, it doesn't, so do it manually:
|
||||||
|
@ -123,7 +122,7 @@ module.exports = React.createClass({
|
||||||
height: displayHeight
|
height: displayHeight
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
var style, res;
|
let style; let res;
|
||||||
|
|
||||||
if (this.props.width && this.props.height) {
|
if (this.props.width && this.props.height) {
|
||||||
style = {
|
style = {
|
||||||
|
@ -133,22 +132,21 @@ module.exports = React.createClass({
|
||||||
res = style.width + "x" + style.height + "px";
|
res = style.width + "x" + style.height + "px";
|
||||||
}
|
}
|
||||||
|
|
||||||
var size;
|
let size;
|
||||||
if (this.props.fileSize) {
|
if (this.props.fileSize) {
|
||||||
size = filesize(this.props.fileSize);
|
size = filesize(this.props.fileSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
var size_res;
|
let size_res;
|
||||||
if (size && res) {
|
if (size && res) {
|
||||||
size_res = size + ", " + res;
|
size_res = size + ", " + res;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
size_res = size || res;
|
size_res = size || res;
|
||||||
}
|
}
|
||||||
|
|
||||||
var showEventMeta = !!this.props.mxEvent;
|
const showEventMeta = !!this.props.mxEvent;
|
||||||
|
|
||||||
var eventMeta;
|
let eventMeta;
|
||||||
if (showEventMeta) {
|
if (showEventMeta) {
|
||||||
// Figure out the sender, defaulting to mxid
|
// Figure out the sender, defaulting to mxid
|
||||||
let sender = this.props.mxEvent.getSender();
|
let sender = this.props.mxEvent.getSender();
|
||||||
|
@ -163,7 +161,7 @@ module.exports = React.createClass({
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventRedact;
|
let eventRedact;
|
||||||
if (showEventMeta) {
|
if (showEventMeta) {
|
||||||
eventRedact = (<div className="mx_ImageView_button" onClick={this.onRedactClick}>
|
eventRedact = (<div className="mx_ImageView_button" onClick={this.onRedactClick}>
|
||||||
{ _t('Remove') }
|
{ _t('Remove') }
|
||||||
|
@ -201,5 +199,5 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,14 +20,14 @@ module.exports = React.createClass({
|
||||||
displayName: 'InlineSpinner',
|
displayName: 'InlineSpinner',
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var w = this.props.w || 16;
|
const w = this.props.w || 16;
|
||||||
var h = this.props.h || 16;
|
const h = this.props.h || 16;
|
||||||
var imgClass = this.props.imgClassName || "";
|
const imgClass = this.props.imgClassName || "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_InlineSpinner">
|
<div className="mx_InlineSpinner">
|
||||||
<img src="img/spinner.gif" width={w} height={h} className={imgClass} />
|
<img src="img/spinner.gif" width={w} height={h} className={imgClass} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -54,7 +54,6 @@ function getOrCreateContainer(containerId) {
|
||||||
* bounding rect as the parent of PE.
|
* bounding rect as the parent of PE.
|
||||||
*/
|
*/
|
||||||
export default class PersistedElement extends React.Component {
|
export default class PersistedElement extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// Unique identifier for this PersistedElement instance
|
// Unique identifier for this PersistedElement instance
|
||||||
// Any PersistedElements with the same persistKey will use
|
// Any PersistedElements with the same persistKey will use
|
||||||
|
|
|
@ -29,7 +29,7 @@ const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
|
||||||
|
|
||||||
// For URLs of matrix.to links in the timeline which have been reformatted by
|
// For URLs of matrix.to links in the timeline which have been reformatted by
|
||||||
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
||||||
const REGEX_LOCAL_MATRIXTO = /^#\/(?:user|room|group)\/(([#!@+])[^\/]*)$/;
|
const REGEX_LOCAL_MATRIXTO = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/;
|
||||||
|
|
||||||
const Pill = React.createClass({
|
const Pill = React.createClass({
|
||||||
statics: {
|
statics: {
|
||||||
|
|
|
@ -16,19 +16,19 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'Spinner',
|
displayName: 'Spinner',
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var w = this.props.w || 32;
|
const w = this.props.w || 32;
|
||||||
var h = this.props.h || 32;
|
const h = this.props.h || 32;
|
||||||
var imgClass = this.props.imgClassName || "";
|
const imgClass = this.props.imgClassName || "";
|
||||||
return (
|
return (
|
||||||
<div className="mx_Spinner">
|
<div className="mx_Spinner">
|
||||||
<img src="img/spinner.gif" width={w} height={h} className={imgClass} />
|
<img src="img/spinner.gif" width={w} height={h} className={imgClass} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,6 @@ import TintableSvg from './TintableSvg';
|
||||||
import AccessibleButton from './AccessibleButton';
|
import AccessibleButton from './AccessibleButton';
|
||||||
|
|
||||||
export default class TintableSvgButton extends React.Component {
|
export default class TintableSvgButton extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default React.createClass({
|
||||||
if (update && PlatformPeg.get()) {
|
if (update && PlatformPeg.get()) {
|
||||||
PlatformPeg.get().installUpdate();
|
PlatformPeg.get().installUpdate();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ export default React.createClass({
|
||||||
if (update && PlatformPeg.get()) {
|
if (update && PlatformPeg.get()) {
|
||||||
PlatformPeg.get().installUpdate();
|
PlatformPeg.get().installUpdate();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -103,5 +103,5 @@ export default React.createClass({
|
||||||
{action_button}
|
{action_button}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,7 +32,7 @@ export default React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
detail: '',
|
detail: '',
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getStatusText: function() {
|
getStatusText: function() {
|
||||||
|
@ -59,7 +59,7 @@ export default React.createClass({
|
||||||
const message = this.getStatusText();
|
const message = this.getStatusText();
|
||||||
const warning = _t('Warning');
|
const warning = _t('Warning');
|
||||||
|
|
||||||
if (!'getUpdateCheckStatusEnum' in PlatformPeg.get()) {
|
if (!('getUpdateCheckStatusEnum' in PlatformPeg.get())) {
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,5 +87,5 @@ export default React.createClass({
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -296,7 +296,7 @@ export const TermsAuthEntry = React.createClass({
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let checkboxes = [];
|
const checkboxes = [];
|
||||||
let allChecked = true;
|
let allChecked = true;
|
||||||
for (const policy of this.state.policies) {
|
for (const policy of this.state.policies) {
|
||||||
const checked = this.state.toggledPolicies[policy.id];
|
const checked = this.state.toggledPolicies[policy.id];
|
||||||
|
@ -306,7 +306,7 @@ export const TermsAuthEntry = React.createClass({
|
||||||
<label key={"policy_checkbox_" + policy.id}>
|
<label key={"policy_checkbox_" + policy.id}>
|
||||||
<input type="checkbox" onClick={() => this._trySubmit(policy.id)} checked={checked} />
|
<input type="checkbox" onClick={() => this._trySubmit(policy.id)} checked={checked} />
|
||||||
<a href={policy.url} target="_blank" rel="noopener">{ policy.name }</a>
|
<a href={policy.url} target="_blank" rel="noopener">{ policy.name }</a>
|
||||||
</label>
|
</label>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ module.exports = React.createClass({
|
||||||
oldCanonicalAlias = this.props.canonicalAliasEvent.getContent().alias;
|
oldCanonicalAlias = this.props.canonicalAliasEvent.getContent().alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newCanonicalAlias = this.state.canonicalAlias;
|
const newCanonicalAlias = this.state.canonicalAlias;
|
||||||
|
|
||||||
if (this.props.canSetCanonicalAlias && oldCanonicalAlias !== newCanonicalAlias) {
|
if (this.props.canSetCanonicalAlias && oldCanonicalAlias !== newCanonicalAlias) {
|
||||||
console.log("AliasSettings: Updating canonical alias");
|
console.log("AliasSettings: Updating canonical alias");
|
||||||
|
@ -167,7 +167,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
if (!this.props.canonicalAlias) {
|
if (!this.props.canonicalAlias) {
|
||||||
this.setState({
|
this.setState({
|
||||||
canonicalAlias: alias
|
canonicalAlias: alias,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { _t } from '../../../languageHandler';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
|
|
||||||
const GROUP_ID_REGEX = /\+\S+\:\S+/;
|
const GROUP_ID_REGEX = /\+\S+:\S+/;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RelatedGroupSettings',
|
displayName: 'RelatedGroupSettings',
|
||||||
|
|
|
@ -33,7 +33,6 @@ import Autocompleter from '../../../autocomplete/Autocompleter';
|
||||||
const COMPOSER_SELECTED = 0;
|
const COMPOSER_SELECTED = 0;
|
||||||
|
|
||||||
export default class Autocomplete extends React.Component {
|
export default class Autocomplete extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
|
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
|
||||||
let image = p["og:image"];
|
let image = p["og:image"];
|
||||||
let imageMaxWidth = 100, imageMaxHeight = 100;
|
const imageMaxWidth = 100; const imageMaxHeight = 100;
|
||||||
if (image && image.startsWith("mxc://")) {
|
if (image && image.startsWith("mxc://")) {
|
||||||
image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight);
|
image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight);
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,7 +269,7 @@ export default class MessageComposer extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let e2eImg, e2eTitle, e2eClass;
|
let e2eImg; let e2eTitle; let e2eClass;
|
||||||
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
||||||
if (roomIsEncrypted) {
|
if (roomIsEncrypted) {
|
||||||
// FIXME: show a /!\ if there are untrusted devices in the room...
|
// FIXME: show a /!\ if there are untrusted devices in the room...
|
||||||
|
@ -429,7 +429,7 @@ export default class MessageComposer extends React.Component {
|
||||||
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
|
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
|
||||||
src="img/icon-text-cancel.svg" />
|
src="img/icon-text-cancel.svg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -67,7 +67,7 @@ const EMOJI_UNICODE_TO_SHORTNAME = mapUnicodeToShort();
|
||||||
const REGEX_EMOJI_WHITESPACE = new RegExp('(?:^|\\s)(' + asciiRegexp + ')\\s$');
|
const REGEX_EMOJI_WHITESPACE = new RegExp('(?:^|\\s)(' + asciiRegexp + ')\\s$');
|
||||||
const EMOJI_REGEX = new RegExp(unicodeRegexp, 'g');
|
const EMOJI_REGEX = new RegExp(unicodeRegexp, 'g');
|
||||||
|
|
||||||
const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000;
|
const TYPING_USER_TIMEOUT = 10000; const TYPING_SERVER_TIMEOUT = 30000;
|
||||||
|
|
||||||
const ENTITY_TYPES = {
|
const ENTITY_TYPES = {
|
||||||
AT_ROOM_PILL: 'ATROOMPILL',
|
AT_ROOM_PILL: 'ATROOMPILL',
|
||||||
|
@ -544,7 +544,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
if (editorState.startText !== null) {
|
if (editorState.startText !== null) {
|
||||||
const text = editorState.startText.text;
|
const text = editorState.startText.text;
|
||||||
const currentStartOffset = editorState.startOffset;
|
const currentStartOffset = editorState.selection.start.offset;
|
||||||
|
|
||||||
// Automatic replacement of plaintext emoji to Unicode emoji
|
// Automatic replacement of plaintext emoji to Unicode emoji
|
||||||
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
|
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
|
||||||
|
@ -558,11 +558,11 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
const range = Range.create({
|
const range = Range.create({
|
||||||
anchor: {
|
anchor: {
|
||||||
key: editorState.selection.startKey,
|
key: editorState.startText.key,
|
||||||
offset: currentStartOffset - emojiMatch[1].length - 1,
|
offset: currentStartOffset - emojiMatch[1].length - 1,
|
||||||
},
|
},
|
||||||
focus: {
|
focus: {
|
||||||
key: editorState.selection.startKey,
|
key: editorState.startText.key,
|
||||||
offset: currentStartOffset - 1,
|
offset: currentStartOffset - 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1078,7 +1078,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
// only look for commands if the first block contains simple unformatted text
|
// only look for commands if the first block contains simple unformatted text
|
||||||
// i.e. no pills or rich-text formatting and begins with a /.
|
// i.e. no pills or rich-text formatting and begins with a /.
|
||||||
let cmd, commandText;
|
let cmd; let commandText;
|
||||||
const firstChild = editorState.document.nodes.get(0);
|
const firstChild = editorState.document.nodes.get(0);
|
||||||
const firstGrandChild = firstChild && firstChild.nodes.get(0);
|
const firstGrandChild = firstChild && firstChild.nodes.get(0);
|
||||||
if (firstChild && firstGrandChild &&
|
if (firstChild && firstGrandChild &&
|
||||||
|
|
|
@ -93,7 +93,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
let joinBlock, previewBlock;
|
let joinBlock; let previewBlock;
|
||||||
|
|
||||||
if (this.props.spinner || this.state.busy) {
|
if (this.props.spinner || this.state.busy) {
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
|
|
@ -657,31 +657,31 @@ module.exports = React.createClass({
|
||||||
const userLevels = powerLevels.users || {};
|
const userLevels = powerLevels.users || {};
|
||||||
|
|
||||||
const powerLevelDescriptors = {
|
const powerLevelDescriptors = {
|
||||||
users_default: {
|
"users_default": {
|
||||||
desc: _t('The default role for new room members is'),
|
desc: _t('The default role for new room members is'),
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
events_default: {
|
"events_default": {
|
||||||
desc: _t('To send messages, you must be a'),
|
desc: _t('To send messages, you must be a'),
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
invite: {
|
"invite": {
|
||||||
desc: _t('To invite users into the room, you must be a'),
|
desc: _t('To invite users into the room, you must be a'),
|
||||||
defaultValue: 50,
|
defaultValue: 50,
|
||||||
},
|
},
|
||||||
state_default: {
|
"state_default": {
|
||||||
desc: _t('To configure the room, you must be a'),
|
desc: _t('To configure the room, you must be a'),
|
||||||
defaultValue: 50,
|
defaultValue: 50,
|
||||||
},
|
},
|
||||||
kick: {
|
"kick": {
|
||||||
desc: _t('To kick users, you must be a'),
|
desc: _t('To kick users, you must be a'),
|
||||||
defaultValue: 50,
|
defaultValue: 50,
|
||||||
},
|
},
|
||||||
ban: {
|
"ban": {
|
||||||
desc: _t('To ban users, you must be a'),
|
desc: _t('To ban users, you must be a'),
|
||||||
defaultValue: 50,
|
defaultValue: 50,
|
||||||
},
|
},
|
||||||
redact: {
|
"redact": {
|
||||||
desc: _t('To remove other users\' messages, you must be a'),
|
desc: _t('To remove other users\' messages, you must be a'),
|
||||||
defaultValue: 50,
|
defaultValue: 50,
|
||||||
},
|
},
|
||||||
|
|
|
@ -290,7 +290,7 @@ module.exports = React.createClass({
|
||||||
for (const i in this.state.vectorContentRules.rules) {
|
for (const i in this.state.vectorContentRules.rules) {
|
||||||
const rule = this.state.vectorContentRules.rules[i];
|
const rule = this.state.vectorContentRules.rules[i];
|
||||||
|
|
||||||
let enabled, actions;
|
let enabled; let actions;
|
||||||
switch (newPushRuleVectorState) {
|
switch (newPushRuleVectorState) {
|
||||||
case PushRuleVectorState.ON:
|
case PushRuleVectorState.ON:
|
||||||
if (rule.actions.length !== 1) {
|
if (rule.actions.length !== 1) {
|
||||||
|
|
106
src/matrix-to.js
106
src/matrix-to.js
|
@ -14,11 +14,24 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import MatrixClientPeg from "./MatrixClientPeg";
|
||||||
|
|
||||||
export const host = "matrix.to";
|
export const host = "matrix.to";
|
||||||
export const baseUrl = `https://${host}`;
|
export const baseUrl = `https://${host}`;
|
||||||
|
|
||||||
|
// The maximum number of servers to pick when working out which servers
|
||||||
|
// to add to permalinks. The servers are appended as ?via=example.org
|
||||||
|
const MAX_SERVER_CANDIDATES = 3;
|
||||||
|
|
||||||
export function makeEventPermalink(roomId, eventId) {
|
export function makeEventPermalink(roomId, eventId) {
|
||||||
return `${baseUrl}/#/${roomId}/${eventId}`;
|
const permalinkBase = `${baseUrl}/#/${roomId}/${eventId}`;
|
||||||
|
|
||||||
|
// If the roomId isn't actually a room ID, don't try to list the servers.
|
||||||
|
// Aliases are already routable, and don't need extra information.
|
||||||
|
if (roomId[0] !== '!') return permalinkBase;
|
||||||
|
|
||||||
|
const serverCandidates = pickServerCandidates(roomId);
|
||||||
|
return `${permalinkBase}${encodeServerCandidates(serverCandidates)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeUserPermalink(userId) {
|
export function makeUserPermalink(userId) {
|
||||||
|
@ -26,9 +39,98 @@ export function makeUserPermalink(userId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeRoomPermalink(roomId) {
|
export function makeRoomPermalink(roomId) {
|
||||||
return `${baseUrl}/#/${roomId}`;
|
const permalinkBase = `${baseUrl}/#/${roomId}`;
|
||||||
|
|
||||||
|
// If the roomId isn't actually a room ID, don't try to list the servers.
|
||||||
|
// Aliases are already routable, and don't need extra information.
|
||||||
|
if (roomId[0] !== '!') return permalinkBase;
|
||||||
|
|
||||||
|
const serverCandidates = pickServerCandidates(roomId);
|
||||||
|
return `${permalinkBase}${encodeServerCandidates(serverCandidates)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeGroupPermalink(groupId) {
|
export function makeGroupPermalink(groupId) {
|
||||||
return `${baseUrl}/#/${groupId}`;
|
return `${baseUrl}/#/${groupId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function encodeServerCandidates(candidates) {
|
||||||
|
if (!candidates || candidates.length === 0) return '';
|
||||||
|
return `?via=${candidates.map(c => encodeURIComponent(c)).join("&via=")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pickServerCandidates(roomId) {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
const room = client.getRoom(roomId);
|
||||||
|
if (!room) return [];
|
||||||
|
|
||||||
|
// Permalinks can have servers appended to them so that the user
|
||||||
|
// receiving them can have a fighting chance at joining the room.
|
||||||
|
// These servers are called "candidates" at this point because
|
||||||
|
// it is unclear whether they are going to be useful to actually
|
||||||
|
// join in the future.
|
||||||
|
//
|
||||||
|
// We pick 3 servers based on the following criteria:
|
||||||
|
//
|
||||||
|
// Server 1: The highest power level user in the room, provided
|
||||||
|
// they are at least PL 50. We don't calculate "what is a moderator"
|
||||||
|
// here because it is less relevant for the vast majority of rooms.
|
||||||
|
// We also want to ensure that we get an admin or high-ranking mod
|
||||||
|
// as they are less likely to leave the room. If no user happens
|
||||||
|
// to meet this criteria, we'll pick the most popular server in the
|
||||||
|
// room.
|
||||||
|
//
|
||||||
|
// Server 2: The next most popular server in the room (in user
|
||||||
|
// distribution). This cannot be the same as Server 1. If no other
|
||||||
|
// servers are available then we'll only return Server 1.
|
||||||
|
//
|
||||||
|
// Server 3: The next most popular server by user distribution. This
|
||||||
|
// has the same rules as Server 2, with the added exception that it
|
||||||
|
// must be unique from Server 1 and 2.
|
||||||
|
|
||||||
|
// Rationale for popular servers: It's hard to get rid of people when
|
||||||
|
// they keep flocking in from a particular server. Sure, the server could
|
||||||
|
// be ACL'd in the future or for some reason be evicted from the room
|
||||||
|
// however an event like that is unlikely the larger the room gets.
|
||||||
|
|
||||||
|
// Note: we don't pick the server the room was created on because the
|
||||||
|
// homeserver should already be using that server as a last ditch attempt
|
||||||
|
// and there's less of a guarantee that the server is a resident server.
|
||||||
|
// Instead, we actively figure out which servers are likely to be residents
|
||||||
|
// in the future and try to use those.
|
||||||
|
|
||||||
|
// Note: Users receiving permalinks that happen to have all 3 potential
|
||||||
|
// servers fail them (in terms of joining) are somewhat expected to hunt
|
||||||
|
// down the person who gave them the link to ask for a participating server.
|
||||||
|
// The receiving user can then manually append the known-good server to
|
||||||
|
// the list and magically have the link work.
|
||||||
|
|
||||||
|
const populationMap: {[server:string]:number} = {};
|
||||||
|
const highestPlUser = {userId: null, powerLevel: 0, serverName: null};
|
||||||
|
|
||||||
|
for (const member of room.getJoinedMembers()) {
|
||||||
|
const serverName = member.userId.split(":").splice(1).join(":");
|
||||||
|
if (member.powerLevel > highestPlUser.powerLevel) {
|
||||||
|
highestPlUser.userId = member.userId;
|
||||||
|
highestPlUser.powerLevel = member.powerLevel;
|
||||||
|
highestPlUser.serverName = serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!populationMap[serverName]) populationMap[serverName] = 0;
|
||||||
|
populationMap[serverName]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates = [];
|
||||||
|
if (highestPlUser.powerLevel >= 50) candidates.push(highestPlUser.serverName);
|
||||||
|
|
||||||
|
const beforePopulation = candidates.length;
|
||||||
|
const serversByPopulation = Object.keys(populationMap)
|
||||||
|
.sort((a, b) => populationMap[b] - populationMap[a])
|
||||||
|
.filter(a => !candidates.includes(a));
|
||||||
|
for (let i = beforePopulation; i <= MAX_SERVER_CANDIDATES; i++) {
|
||||||
|
const idx = i - beforePopulation;
|
||||||
|
if (idx >= serversByPopulation.length) break;
|
||||||
|
candidates.push(serversByPopulation[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var NotificationUtils = require('./NotificationUtils');
|
const NotificationUtils = require('./NotificationUtils');
|
||||||
|
|
||||||
var encodeActions = NotificationUtils.encodeActions;
|
const encodeActions = NotificationUtils.encodeActions;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ACTION_NOTIFY: encodeActions({notify: true}),
|
ACTION_NOTIFY: encodeActions({notify: true}),
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const PHONE_NUMBER_REGEXP = /^[0-9 -\.]+$/;
|
const PHONE_NUMBER_REGEXP = /^[0-9 -.]+$/;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Do basic validation to determine if the given input could be
|
* Do basic validation to determine if the given input could be
|
||||||
|
|
|
@ -23,7 +23,6 @@ limitations under the License.
|
||||||
* intended to handle environmental factors for specific settings.
|
* intended to handle environmental factors for specific settings.
|
||||||
*/
|
*/
|
||||||
export default class SettingController {
|
export default class SettingController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the overridden value for the setting, if any. This must return null if the
|
* Gets the overridden value for the setting, if any. This must return null if the
|
||||||
* value is not to be overridden, otherwise it must return the new value.
|
* value is not to be overridden, otherwise it must return the new value.
|
||||||
|
|
|
@ -23,7 +23,6 @@ import Unread from '../Unread';
|
||||||
* the RoomList.
|
* the RoomList.
|
||||||
*/
|
*/
|
||||||
class RoomListStore extends Store {
|
class RoomListStore extends Store {
|
||||||
|
|
||||||
static _listOrders = {
|
static _listOrders = {
|
||||||
"m.favourite": "manual",
|
"m.favourite": "manual",
|
||||||
"im.vector.fake.invite": "recent",
|
"im.vector.fake.invite": "recent",
|
||||||
|
|
|
@ -77,7 +77,7 @@ const ALLOWED_BLOB_MIMETYPES = {
|
||||||
'audio/x-pn-wav': true,
|
'audio/x-pn-wav': true,
|
||||||
'audio/flac': true,
|
'audio/flac': true,
|
||||||
'audio/x-flac': true,
|
'audio/x-flac': true,
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt a file attached to a matrix event.
|
* Decrypt a file attached to a matrix event.
|
||||||
|
|
349
test/matrix-to-test.js
Normal file
349
test/matrix-to-test.js
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 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 expect from 'expect';
|
||||||
|
import peg from '../src/MatrixClientPeg';
|
||||||
|
import {
|
||||||
|
makeEventPermalink,
|
||||||
|
makeGroupPermalink,
|
||||||
|
makeRoomPermalink,
|
||||||
|
makeUserPermalink,
|
||||||
|
pickServerCandidates,
|
||||||
|
} from "../src/matrix-to";
|
||||||
|
import * as testUtils from "./test-utils";
|
||||||
|
|
||||||
|
|
||||||
|
describe('matrix-to', function() {
|
||||||
|
let sandbox;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
testUtils.beforeEach(this);
|
||||||
|
sandbox = testUtils.stubClient();
|
||||||
|
peg.get().credentials = { userId: "@test:example.com" };
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pick no candidate servers when the room is not found', function() {
|
||||||
|
peg.get().getRoom = () => null;
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pick no candidate servers when the room has no members', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pick a candidate server for the highest power level user in the room', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:pl_50",
|
||||||
|
powerLevel: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@alice:pl_75",
|
||||||
|
powerLevel: 75,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@alice:pl_95",
|
||||||
|
powerLevel: 95,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(3);
|
||||||
|
expect(pickedServers[0]).toBe("pl_95");
|
||||||
|
// we don't check the 2nd and 3rd servers because that is done by the next test
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pick candidate servers based on user population', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:first",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:first",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@charlie:first",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@alice:second",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:second",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@charlie:third",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(3);
|
||||||
|
expect(pickedServers[0]).toBe("first");
|
||||||
|
expect(pickedServers[1]).toBe("second");
|
||||||
|
expect(pickedServers[2]).toBe("third");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pick prefer candidate servers with higher power levels', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:first",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@alice:second",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:second",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@charlie:third",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(3);
|
||||||
|
expect(pickedServers[0]).toBe("first");
|
||||||
|
expect(pickedServers[1]).toBe("second");
|
||||||
|
expect(pickedServers[2]).toBe("third");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with IPv4 hostnames', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:127.0.0.1",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(1);
|
||||||
|
expect(pickedServers[0]).toBe("127.0.0.1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with IPv6 hostnames', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:[::1]",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(1);
|
||||||
|
expect(pickedServers[0]).toBe("[::1]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with IPv4 hostnames with ports', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:127.0.0.1:8448",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(1);
|
||||||
|
expect(pickedServers[0]).toBe("127.0.0.1:8448");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with IPv6 hostnames with ports', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:[::1]:8448",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(1);
|
||||||
|
expect(pickedServers[0]).toBe("[::1]:8448");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with hostnames with ports', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:example.org:8448",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(1);
|
||||||
|
expect(pickedServers[0]).toBe("example.org:8448");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate an event permalink for room IDs with no candidate servers', function() {
|
||||||
|
peg.get().getRoom = () => null;
|
||||||
|
const result = makeEventPermalink("!somewhere:example.org", "$something:example.com");
|
||||||
|
expect(result).toBe("https://matrix.to/#/!somewhere:example.org/$something:example.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate an event permalink for room IDs with some candidate servers', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:first",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:second",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const result = makeEventPermalink("!somewhere:example.org", "$something:example.com");
|
||||||
|
expect(result).toBe("https://matrix.to/#/!somewhere:example.org/$something:example.com?via=first&via=second");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a room permalink for room IDs with no candidate servers', function() {
|
||||||
|
peg.get().getRoom = () => null;
|
||||||
|
const result = makeRoomPermalink("!somewhere:example.org");
|
||||||
|
expect(result).toBe("https://matrix.to/#/!somewhere:example.org");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a room permalink for room IDs with some candidate servers', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:first",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:second",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const result = makeRoomPermalink("!somewhere:example.org");
|
||||||
|
expect(result).toBe("https://matrix.to/#/!somewhere:example.org?via=first&via=second");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Technically disallowed but we'll test it anyways
|
||||||
|
it('should generate an event permalink for room aliases with no candidate servers', function() {
|
||||||
|
peg.get().getRoom = () => null;
|
||||||
|
const result = makeEventPermalink("#somewhere:example.org", "$something:example.com");
|
||||||
|
expect(result).toBe("https://matrix.to/#/#somewhere:example.org/$something:example.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Technically disallowed but we'll test it anyways
|
||||||
|
it('should generate an event permalink for room aliases without candidate servers', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:first",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:second",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const result = makeEventPermalink("#somewhere:example.org", "$something:example.com");
|
||||||
|
expect(result).toBe("https://matrix.to/#/#somewhere:example.org/$something:example.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a room permalink for room aliases with no candidate servers', function() {
|
||||||
|
peg.get().getRoom = () => null;
|
||||||
|
const result = makeRoomPermalink("#somewhere:example.org");
|
||||||
|
expect(result).toBe("https://matrix.to/#/#somewhere:example.org");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a room permalink for room aliases without candidate servers', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:first",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:second",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const result = makeRoomPermalink("#somewhere:example.org");
|
||||||
|
expect(result).toBe("https://matrix.to/#/#somewhere:example.org");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a user permalink', function() {
|
||||||
|
const result = makeUserPermalink("@someone:example.org");
|
||||||
|
expect(result).toBe("https://matrix.to/#/@someone:example.org");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a group permalink', function() {
|
||||||
|
const result = makeGroupPermalink("+community:example.org");
|
||||||
|
expect(result).toBe("https://matrix.to/#/+community:example.org");
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue