Merge branch 'experimental' into bwindels/rightpanelbelowheader
						commit
						b4dd739771
					
				|  | @ -7,11 +7,8 @@ src/component-index.js | |||
| src/components/structures/BottomLeftMenu.js | ||||
| src/components/structures/CompatibilityPage.js | ||||
| src/components/structures/CreateRoom.js | ||||
| src/components/structures/HomePage.js | ||||
| src/components/structures/LeftPanel.js | ||||
| src/components/structures/LoggedInView.js | ||||
| src/components/structures/login/ForgotPassword.js | ||||
| src/components/structures/LoginBox.js | ||||
| src/components/structures/MessagePanel.js | ||||
| src/components/structures/NotificationPanel.js | ||||
| src/components/structures/RoomDirectory.js | ||||
|  | @ -22,22 +19,17 @@ src/components/structures/SearchBox.js | |||
| src/components/structures/TimelinePanel.js | ||||
| src/components/structures/UploadBar.js | ||||
| src/components/structures/UserSettings.js | ||||
| src/components/structures/ViewSource.js | ||||
| src/components/views/avatars/BaseAvatar.js | ||||
| src/components/views/avatars/MemberAvatar.js | ||||
| src/components/views/create_room/RoomAlias.js | ||||
| src/components/views/dialogs/ChangelogDialog.js | ||||
| src/components/views/dialogs/DeactivateAccountDialog.js | ||||
| src/components/views/dialogs/SetPasswordDialog.js | ||||
| src/components/views/dialogs/UnknownDeviceDialog.js | ||||
| src/components/views/directory/NetworkDropdown.js | ||||
| src/components/views/elements/AddressSelector.js | ||||
| src/components/views/elements/DeviceVerifyButtons.js | ||||
| src/components/views/elements/DirectorySearchBox.js | ||||
| src/components/views/elements/ImageView.js | ||||
| src/components/views/elements/InlineSpinner.js | ||||
| src/components/views/elements/MemberEventListSummary.js | ||||
| src/components/views/elements/Spinner.js | ||||
| src/components/views/elements/TintableSvg.js | ||||
| src/components/views/elements/UserSelector.js | ||||
| src/components/views/globals/MatrixToolbar.js | ||||
|  | @ -90,7 +82,6 @@ src/MatrixClientPeg.js | |||
| src/Modal.js | ||||
| src/notifications/ContentRules.js | ||||
| src/notifications/PushRuleVectorState.js | ||||
| src/notifications/StandardActions.js | ||||
| src/notifications/VectorPushRulesDefinitions.js | ||||
| src/Notifier.js | ||||
| src/PlatformPeg.js | ||||
|  | @ -111,7 +102,6 @@ src/utils/MultiInviter.js | |||
| src/utils/Receipt.js | ||||
| src/VectorConferenceHandler.js | ||||
| src/Velociraptor.js | ||||
| src/VelocityBounce.js | ||||
| src/WhoIsTyping.js | ||||
| src/wrappers/withMatrixClient.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) | ||||
| ===================================================================================================== | ||||
| [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'), | ||||
|                     '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: [ | ||||
|                     path.resolve('./test'), | ||||
|                     "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', | ||||
|             externals: { | ||||
|                 // Don't try to bundle electron: leave it as a commonjs dependency
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "matrix-react-sdk", | ||||
|   "version": "0.14.1", | ||||
|   "version": "0.14.2", | ||||
|   "description": "SDK for matrix.org using React", | ||||
|   "author": "matrix.org", | ||||
|   "repository": { | ||||
|  | @ -76,6 +76,7 @@ | |||
|     "lodash": "^4.13.1", | ||||
|     "lolex": "2.3.2", | ||||
|     "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", | ||||
|     "memfs": "^2.10.1", | ||||
|     "optimist": "^0.6.1", | ||||
|     "pako": "^1.0.5", | ||||
|     "prop-types": "^15.5.8", | ||||
|  | @ -100,7 +101,7 @@ | |||
|   "devDependencies": { | ||||
|     "babel-cli": "^6.26.0", | ||||
|     "babel-core": "^6.26.3", | ||||
|     "babel-eslint": "^6.1.2", | ||||
|     "babel-eslint": "^10.0.1", | ||||
|     "babel-loader": "^7.1.5", | ||||
|     "babel-plugin-add-module-exports": "^0.2.1", | ||||
|     "babel-plugin-transform-async-to-bluebird": "^1.1.1", | ||||
|  | @ -114,9 +115,9 @@ | |||
|     "babel-preset-react": "^6.24.1", | ||||
|     "chokidar": "^1.6.1", | ||||
|     "concurrently": "^4.0.1", | ||||
|     "eslint": "^3.13.1", | ||||
|     "eslint": "^5.8.0", | ||||
|     "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-react": "^7.7.0", | ||||
|     "estree-walker": "^0.5.0", | ||||
|  |  | |||
|  | @ -167,8 +167,7 @@ textarea { | |||
|     font-weight: 300; | ||||
|     font-size: 15px; | ||||
|     position: relative; | ||||
|     padding-left: 58px; | ||||
|     padding-bottom: 36px; | ||||
|     padding: 0 58px 36px; | ||||
|     width: 60%; | ||||
|     max-width: 704px; | ||||
|     box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2); | ||||
|  | @ -213,14 +212,13 @@ textarea { | |||
| } | ||||
| 
 | ||||
| .mx_Dialog_content { | ||||
|     margin: 24px 58px 68px 0; | ||||
|     margin: 24px 0 68px; | ||||
|     font-size: 14px; | ||||
|     color: $primary-fg-color; | ||||
|     word-wrap: break-word; | ||||
| } | ||||
| 
 | ||||
| .mx_Dialog_buttons { | ||||
|     padding-right: 58px; | ||||
|     text-align: right; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,6 +25,10 @@ limitations under the License. | |||
|     line-height: 16px; | ||||
| } | ||||
| 
 | ||||
| .mx_TagTileContextMenu_item object { | ||||
|     pointer-events: none; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .mx_TagTileContextMenu_item_icon { | ||||
|     padding-right: 8px; | ||||
|  |  | |||
|  | @ -14,10 +14,6 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| .mx_DevTools_dialog { | ||||
|     padding-right: 58px; | ||||
| } | ||||
| 
 | ||||
| .mx_DevTools_content { | ||||
|     margin: 10px 0; | ||||
| } | ||||
|  |  | |||
|  | @ -14,11 +14,6 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| .mx_ShareDialog { | ||||
|     // this is to center the content | ||||
|     padding-right: 58px; | ||||
| } | ||||
| 
 | ||||
| .mx_ShareDialog hr { | ||||
|     margin-top: 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 | ||||
|     // important it is. | ||||
|     height: 100%; | ||||
| 
 | ||||
|     // position the gemini scrollbar nicely | ||||
|     padding-right: 58px; | ||||
| } | ||||
| 
 | ||||
| .mx_UnknownDeviceDialog { | ||||
|  | @ -51,4 +48,4 @@ limitations under the License. | |||
| .mx_UnknownDeviceDialog .mx_UnknownDeviceDialog_deviceList li { | ||||
|     height: 40px; | ||||
|     border-bottom: 1px solid $primary-hairline-color; | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| /* | ||||
| Copyright 2107 Vector Creations Ltd | ||||
| Copyright 2017 Vector Creations Ltd | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| /* | ||||
| 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"); | ||||
| 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'; | ||||
| 
 | ||||
| class HistoryItem { | ||||
| 
 | ||||
|     // We store history items in their native format to ensure history is accurate
 | ||||
|     // and then convert them if our RTE has subsequently changed format.
 | ||||
|     value: Value; | ||||
|  |  | |||
|  | @ -78,7 +78,6 @@ class MemberEntity extends Entity { | |||
| } | ||||
| 
 | ||||
| class UserEntity extends Entity { | ||||
| 
 | ||||
|     constructor(model, showInviteButton, inviteFn) { | ||||
|         super(model); | ||||
|         this.showInviteButton = Boolean(showInviteButton); | ||||
|  |  | |||
|  | @ -64,7 +64,7 @@ export function containsEmoji(str) { | |||
|  * because we want to include emoji shortnames in title text | ||||
|  */ | ||||
| function unicodeToImage(str) { | ||||
|     let replaceWith, unicode, alt, short, fname; | ||||
|     let replaceWith; let unicode; let alt; let short; let fname; | ||||
|     const mappedUnicode = emojione.mapUnicodeToShort(); | ||||
| 
 | ||||
|     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. | ||||
|  */ | ||||
| class PasswordReset { | ||||
| 
 | ||||
|     /** | ||||
|      * Configure the endpoints for password resetting. | ||||
|      * @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"]; | ||||
| 
 | ||||
| class Presence { | ||||
| 
 | ||||
|     /** | ||||
|      * Start listening the user activity to evaluate his presence state. | ||||
|      * 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. | ||||
|  */ | ||||
| export function getOnlyOtherMember(room, myUserId) { | ||||
| 
 | ||||
|     if (room.currentState.getJoinedMemberCount() === 2) { | ||||
|         return room.getJoinedMembers().filter(function(m) { | ||||
|             return m.userId !== myUserId; | ||||
|  | @ -103,7 +102,7 @@ export function guessAndSetDMRoom(room, isDirect) { | |||
|     let newTarget; | ||||
|     if (isDirect) { | ||||
|         const guessedUserId = guessDMRoomTargetId( | ||||
|             room, MatrixClientPeg.get().getUserId() | ||||
|             room, MatrixClientPeg.get().getUserId(), | ||||
|         ); | ||||
|         newTarget = guessedUserId; | ||||
|     } else { | ||||
|  |  | |||
|  | @ -22,7 +22,6 @@ const SdkConfig = require('./SdkConfig'); | |||
| const MatrixClientPeg = require('./MatrixClientPeg'); | ||||
| 
 | ||||
| class ScalarAuthClient { | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.scalarToken = null; | ||||
|     } | ||||
|  |  | |||
|  | @ -24,7 +24,6 @@ const DEFAULTS = { | |||
| }; | ||||
| 
 | ||||
| class SdkConfig { | ||||
| 
 | ||||
|     static get() { | ||||
|         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) | ||||
|  */ | ||||
| class UserActivity { | ||||
| 
 | ||||
|     /** | ||||
|      * Start listening to user activity | ||||
|      */ | ||||
|  |  | |||
|  | @ -87,7 +87,7 @@ export default { | |||
|             device_display_name: address, | ||||
|             lang: navigator.language, | ||||
|             data: data, | ||||
|             append: true,  // We always append for email pushers since we don't want to stop other accounts notifying to the same email address
 | ||||
|             append: true, // We always append for email pushers since we don't want to stop other accounts notifying to the same email address
 | ||||
|         }); | ||||
|     }, | ||||
| }; | ||||
|  |  | |||
|  | @ -3,8 +3,8 @@ const Velocity = require('velocity-vector'); | |||
| // courtesy of https://github.com/julianshapiro/velocity/issues/283
 | ||||
| // We only use easeOutBounce (easeInBounce is just sort of nonsensical)
 | ||||
| function bounce( p ) { | ||||
|     let pow2, | ||||
|         bounce = 4; | ||||
|     let pow2; | ||||
|     let bounce = 4; | ||||
| 
 | ||||
|     while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) { | ||||
|         // just sets pow2
 | ||||
|  |  | |||
|  | @ -85,7 +85,7 @@ export default class Autocompleter { | |||
|                 provider | ||||
|                     .getCompletions(query, selection, force) | ||||
|                     .timeout(PROVIDER_COMPLETION_TIMEOUT) | ||||
|                     .reflect() | ||||
|                     .reflect(), | ||||
|             ), | ||||
|         ); | ||||
| 
 | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ export default class CommunityProvider extends AutocompleteProvider { | |||
|         if (command) { | ||||
|             const joinedGroups = cli.getGroups().filter(({myMembership}) => myMembership === 'join'); | ||||
| 
 | ||||
|             const groups = (await Promise.all(joinedGroups.map(async ({groupId}) => { | ||||
|             const groups = (await Promise.all(joinedGroups.map(async({groupId}) => { | ||||
|                 try { | ||||
|                     return FlairStore.getGroupProfileCached(cli, groupId); | ||||
|                 } catch (e) { // if FlairStore failed, fall back to just groupId
 | ||||
|  |  | |||
|  | @ -26,7 +26,6 @@ import { Block } from 'slate'; | |||
|  */ | ||||
| 
 | ||||
| class PlainWithPillsSerializer { | ||||
| 
 | ||||
|     /* | ||||
|      * @param {String} options.pillFormat - either 'md', 'plain', 'id' | ||||
|      */ | ||||
|  |  | |||
|  | @ -33,12 +33,12 @@ module.exports = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     getInitialState: function() { | ||||
|         return({ | ||||
|             directoryHover : false, | ||||
|             roomsHover : false, | ||||
|         return ({ | ||||
|             directoryHover: false, | ||||
|             roomsHover: false, | ||||
|             homeHover: false, | ||||
|             peopleHover : false, | ||||
|             settingsHover : false, | ||||
|             peopleHover: false, | ||||
|             settingsHover: false, | ||||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|  | @ -145,7 +145,7 @@ module.exports = React.createClass({ | |||
|     // Get the label/tooltip to show
 | ||||
|     getLabel: function(label, show) { | ||||
|         if (show) { | ||||
|             var RoomTooltip = sdk.getComponent("rooms.RoomTooltip"); | ||||
|             const RoomTooltip = sdk.getComponent("rooms.RoomTooltip"); | ||||
|             return <RoomTooltip className="mx_BottomLeftMenu_tooltip" label={label} />; | ||||
|         } | ||||
|     }, | ||||
|  |  | |||
|  | @ -16,18 +16,18 @@ limitations under the License. | |||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| var React = require('react'); | ||||
| const React = require('react'); | ||||
| import { _t } from '../../languageHandler'; | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'CompatibilityPage', | ||||
|     propTypes: { | ||||
|         onAccept: React.PropTypes.func | ||||
|         onAccept: React.PropTypes.func, | ||||
|     }, | ||||
| 
 | ||||
|     getDefaultProps: function() { | ||||
|         return { | ||||
|             onAccept: function() {} // NOP
 | ||||
|             onAccept: function() {}, // NOP
 | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|  | @ -36,7 +36,6 @@ module.exports = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
| 
 | ||||
|         return ( | ||||
|         <div className="mx_CompatibilityPage"> | ||||
|             <div className="mx_CompatibilityPage_box"> | ||||
|  | @ -69,5 +68,5 @@ module.exports = React.createClass({ | |||
|             </div> | ||||
|         </div> | ||||
|         ); | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -780,7 +780,7 @@ export default React.createClass({ | |||
|             ), | ||||
|             button: _t("Leave"), | ||||
|             danger: this.state.isUserPrivileged, | ||||
|             onFinished: async (confirmed) => { | ||||
|             onFinished: async(confirmed) => { | ||||
|                 if (!confirmed) return; | ||||
| 
 | ||||
|                 this.setState({membershipBusy: true}); | ||||
|  |  | |||
|  | @ -52,15 +52,14 @@ class HomePage extends React.Component { | |||
| 
 | ||||
|         if (this.props.teamToken && this.props.teamServerUrl) { | ||||
|             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
 | ||||
|             // so that it can inherit CSS and theming easily rather than mess around
 | ||||
|             // with iframes and trying to synchronise document.stylesheets.
 | ||||
| 
 | ||||
|             let src = this.props.homePageUrl || 'home.html'; | ||||
|             const src = this.props.homePageUrl || 'home.html'; | ||||
| 
 | ||||
|             request( | ||||
|                 { 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)); | ||||
|                     this.setState({ page: body }); | ||||
|                 } | ||||
|                 }, | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | @ -93,8 +92,7 @@ class HomePage extends React.Component { | |||
|                     <iframe src={ this.state.iframeSrc } /> | ||||
|                 </div> | ||||
|             ); | ||||
|         } | ||||
|         else { | ||||
|         } else { | ||||
|             const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); | ||||
|             return ( | ||||
|                 <GeminiScrollbarWrapper autoshow={true} className="mx_HomePage"> | ||||
|  | @ -106,4 +104,4 @@ class HomePage extends React.Component { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = HomePage; | ||||
| module.exports = HomePage; | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ import VectorConferenceHandler from '../../VectorConferenceHandler'; | |||
| import SettingsStore from '../../settings/SettingsStore'; | ||||
| 
 | ||||
| 
 | ||||
| var LeftPanel = React.createClass({ | ||||
| const LeftPanel = React.createClass({ | ||||
|     displayName: 'LeftPanel', | ||||
| 
 | ||||
|     // NB. If you add props, don't forget to update
 | ||||
|  | @ -214,7 +214,7 @@ var LeftPanel = React.createClass({ | |||
|             </div> | ||||
|         ); | ||||
|         // <BottomLeftMenu collapsed={this.props.collapsed}/>
 | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
| 
 | ||||
| module.exports = LeftPanel; | ||||
|  |  | |||
|  | @ -65,6 +65,9 @@ const LoggedInView = React.createClass({ | |||
| 
 | ||||
|         teamToken: PropTypes.string, | ||||
| 
 | ||||
|         // Used by the RoomView to handle joining rooms
 | ||||
|         viaServers: PropTypes.arrayOf(PropTypes.string), | ||||
| 
 | ||||
|         // and lots and lots of other stuff.
 | ||||
|     }, | ||||
| 
 | ||||
|  | @ -433,6 +436,7 @@ const LoggedInView = React.createClass({ | |||
|                         onRegistered={this.props.onRegistered} | ||||
|                         thirdPartyInvite={this.props.thirdPartyInvite} | ||||
|                         oobData={this.props.roomOobData} | ||||
|                         viaServers={this.props.viaServers} | ||||
|                         eventPixelOffset={this.props.initialEventPixelOffset} | ||||
|                         key={this.props.currentRoomId || 'roomview'} | ||||
|                         disabled={this.props.middleDisabled} | ||||
|  |  | |||
|  | @ -53,5 +53,5 @@ module.exports = React.createClass({ | |||
|                 { loginButton } | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -840,6 +840,7 @@ export default React.createClass({ | |||
|             page_type: PageTypes.RoomView, | ||||
|             thirdPartyInvite: roomInfo.third_party_invite, | ||||
|             roomOobData: roomInfo.oob_data, | ||||
|             viaServers: roomInfo.via_servers, | ||||
|         }; | ||||
| 
 | ||||
|         if (roomInfo.room_alias) { | ||||
|  | @ -1489,9 +1490,21 @@ export default React.createClass({ | |||
|                 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 = { | ||||
|                 action: 'view_room', | ||||
|                 event_id: eventId, | ||||
|                 via_servers: via, | ||||
|                 // 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
 | ||||
|                 // associated EventTile.
 | ||||
|  |  | |||
|  | @ -16,18 +16,18 @@ limitations under the License. | |||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| var React = require('react'); | ||||
| const React = require('react'); | ||||
| 
 | ||||
| var MatrixClientPeg = require('../../MatrixClientPeg'); | ||||
| var ContentRepo = require("matrix-js-sdk").ContentRepo; | ||||
| var Modal = require('../../Modal'); | ||||
| var sdk = require('../../index'); | ||||
| var dis = require('../../dispatcher'); | ||||
| const MatrixClientPeg = require('../../MatrixClientPeg'); | ||||
| const ContentRepo = require("matrix-js-sdk").ContentRepo; | ||||
| const Modal = require('../../Modal'); | ||||
| const sdk = require('../../index'); | ||||
| const dis = require('../../dispatcher'); | ||||
| 
 | ||||
| var linkify = require('linkifyjs'); | ||||
| var linkifyString = require('linkifyjs/string'); | ||||
| var linkifyMatrix = require('../../linkify-matrix'); | ||||
| var sanitizeHtml = require('sanitize-html'); | ||||
| const linkify = require('linkifyjs'); | ||||
| const linkifyString = require('linkifyjs/string'); | ||||
| const linkifyMatrix = require('../../linkify-matrix'); | ||||
| const sanitizeHtml = require('sanitize-html'); | ||||
| import Promise from 'bluebird'; | ||||
| 
 | ||||
| import { _t } from '../../languageHandler'; | ||||
|  | @ -46,7 +46,7 @@ module.exports = React.createClass({ | |||
|     getDefaultProps: function() { | ||||
|         return { | ||||
|             config: {}, | ||||
|         } | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     getInitialState: function() { | ||||
|  | @ -58,7 +58,7 @@ module.exports = React.createClass({ | |||
|             includeAll: false, | ||||
|             roomServer: null, | ||||
|             filterString: null, | ||||
|         } | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     componentWillMount: function() { | ||||
|  | @ -134,13 +134,12 @@ module.exports = React.createClass({ | |||
|             opts.include_all_networks = true; | ||||
|         } | ||||
|         if (this.nextBatch) opts.since = this.nextBatch; | ||||
|         if (my_filter_string) opts.filter = { generic_search_term: my_filter_string } ; | ||||
|         if (my_filter_string) opts.filter = { generic_search_term: my_filter_string }; | ||||
|         return MatrixClientPeg.get().publicRooms(opts).then((data) => { | ||||
|             if ( | ||||
|                 my_filter_string != this.state.filterString || | ||||
|                 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,
 | ||||
|                 // throw away the result (don't even clear the busy flag
 | ||||
|                 // since we must still have a request in flight)
 | ||||
|  | @ -163,8 +162,7 @@ module.exports = React.createClass({ | |||
|             if ( | ||||
|                 my_filter_string != this.state.filterString || | ||||
|                 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
 | ||||
|                 // requests either
 | ||||
|                 return; | ||||
|  | @ -177,10 +175,10 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|             this.setState({ loading: false }); | ||||
|             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, { | ||||
|                 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. | ||||
|      */ | ||||
|     removeFromDirectory: function(room) { | ||||
|         var alias = get_display_alias_for_room(room); | ||||
|         var name = room.name || alias || _t('Unnamed room'); | ||||
|         const alias = get_display_alias_for_room(room); | ||||
|         const name = room.name || alias || _t('Unnamed room'); | ||||
| 
 | ||||
|         var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); | ||||
|         var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); | ||||
|         const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); | ||||
|         const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); | ||||
| 
 | ||||
|         var desc; | ||||
|         let desc; | ||||
|         if (alias) { | ||||
|             desc = _t('Delete the room alias %(alias)s and remove %(name)s from the directory?', {alias: alias, name: name}); | ||||
|         } else { | ||||
|  | @ -212,9 +210,9 @@ module.exports = React.createClass({ | |||
|             onFinished: (should_delete) => { | ||||
|                 if (!should_delete) return; | ||||
| 
 | ||||
|                 var Loader = sdk.getComponent("elements.Spinner"); | ||||
|                 var modal = Modal.createDialog(Loader); | ||||
|                 var step = _t('remove %(name)s from the directory.', {name: name}); | ||||
|                 const Loader = sdk.getComponent("elements.Spinner"); | ||||
|                 const modal = Modal.createDialog(Loader); | ||||
|                 let step = _t('remove %(name)s from the directory.', {name: name}); | ||||
| 
 | ||||
|                 MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => { | ||||
|                     if (!alias) return; | ||||
|  | @ -229,10 +227,10 @@ module.exports = React.createClass({ | |||
|                     console.error("Failed to " + step + ": " + err); | ||||
|                     Modal.createTrackedDialog('Remove from Directory Error', '', ErrorDialog, { | ||||
|                         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) { | ||||
|         var payload = {action: 'view_room'}; | ||||
|         const payload = {action: 'view_room'}; | ||||
|         if (room) { | ||||
|             // 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
 | ||||
|  | @ -383,16 +381,16 @@ module.exports = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     getRows: function() { | ||||
|         var BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); | ||||
|         const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); | ||||
| 
 | ||||
|         if (!this.state.publicRooms) return []; | ||||
| 
 | ||||
|         var rooms = this.state.publicRooms; | ||||
|         var rows = []; | ||||
|         var self = this; | ||||
|         var guestRead, guestJoin, perms; | ||||
|         for (var i = 0; i < rooms.length; i++) { | ||||
|             var name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room'); | ||||
|         const rooms = this.state.publicRooms; | ||||
|         const rows = []; | ||||
|         const self = this; | ||||
|         let guestRead; let guestJoin; let perms; | ||||
|         for (let i = 0; i < rooms.length; i++) { | ||||
|             const name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room'); | ||||
|             guestRead = null; | ||||
|             guestJoin = null; | ||||
| 
 | ||||
|  | @ -412,7 +410,7 @@ module.exports = React.createClass({ | |||
|                 perms = <div className="mx_RoomDirectory_perms">{guestRead}{guestJoin}</div>; | ||||
|             } | ||||
| 
 | ||||
|             var topic = rooms[i].topic || ''; | ||||
|             let topic = rooms[i].topic || ''; | ||||
|             topic = linkifyString(sanitizeHtml(topic)); | ||||
| 
 | ||||
|             rows.push( | ||||
|  | @ -432,14 +430,14 @@ module.exports = React.createClass({ | |||
|                         <div className="mx_RoomDirectory_name">{ name }</div>  | ||||
|                         { perms } | ||||
|                         <div className="mx_RoomDirectory_topic" | ||||
|                              onClick={ function(e) { e.stopPropagation() } } | ||||
|                              dangerouslySetInnerHTML={{ __html: topic }}/> | ||||
|                              onClick={ function(e) { e.stopPropagation(); } } | ||||
|                              dangerouslySetInnerHTML={{ __html: topic }} /> | ||||
|                         <div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(rooms[i]) }</div> | ||||
|                     </td> | ||||
|                     <td className="mx_RoomDirectory_roomMemberCount"> | ||||
|                         { rooms[i].num_joined_members } | ||||
|                     </td> | ||||
|                 </tr> | ||||
|                 </tr>, | ||||
|             ); | ||||
|         } | ||||
|         return rows; | ||||
|  | @ -524,7 +522,7 @@ module.exports = React.createClass({ | |||
|                 onFillRequest={ this.onFillRequest } | ||||
|                 stickyBottom={false} | ||||
|                 startAtBottom={false} | ||||
|                 onResize={function(){}} | ||||
|                 onResize={function() {}} | ||||
|             > | ||||
|                 { scrollpanel_content } | ||||
|             </ScrollPanel>; | ||||
|  | @ -577,11 +575,11 @@ module.exports = React.createClass({ | |||
|                 </div> | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
| 
 | ||||
| // Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
 | ||||
| // but works with the objects we get from the public room list
 | ||||
| function get_display_alias_for_room(room) { | ||||
|     return  room.canonical_alias || (room.aliases ? room.aliases[0] : ""); | ||||
|     return room.canonical_alias || (room.aliases ? room.aliases[0] : ""); | ||||
| } | ||||
|  |  | |||
|  | @ -37,7 +37,7 @@ function getUnsentMessages(room) { | |||
|     return room.getPendingEvents().filter(function(ev) { | ||||
|         return ev.status === Matrix.EventStatus.NOT_SENT; | ||||
|     }); | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'RoomStatusBar', | ||||
|  | @ -303,7 +303,7 @@ module.exports = React.createClass({ | |||
|         const errorIsMauError = Boolean( | ||||
|             this.state.syncStateData && | ||||
|             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; | ||||
|     }, | ||||
|  |  | |||
|  | @ -90,6 +90,9 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|         // is the RightPanel collapsed?
 | ||||
|         collapsedRhs: PropTypes.bool, | ||||
| 
 | ||||
|         // Servers the RoomView can use to try and assist joins
 | ||||
|         viaServers: PropTypes.arrayOf(PropTypes.string), | ||||
|     }, | ||||
| 
 | ||||
|     getInitialState: function() { | ||||
|  | @ -835,7 +838,7 @@ module.exports = React.createClass({ | |||
|                 action: 'do_after_sync_prepared', | ||||
|                 deferred_action: { | ||||
|                     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; | ||||
|             dis.dispatch({ | ||||
|                 action: 'join_room', | ||||
|                 opts: { inviteSignUrl: signUrl }, | ||||
|                 opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers }, | ||||
|             }); | ||||
|             return Promise.resolve(); | ||||
|         }); | ||||
|  | @ -1676,7 +1679,7 @@ module.exports = React.createClass({ | |||
|             </AuxPanel> | ||||
|         ); | ||||
| 
 | ||||
|         let messageComposer, searchInfo; | ||||
|         let messageComposer; let searchInfo; | ||||
|         const canSpeak = ( | ||||
|             // joined and not showing search results
 | ||||
|             myMembership === 'join' && !this.state.searchResults | ||||
|  | @ -1695,7 +1698,7 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|         if (MatrixClientPeg.get().isGuest()) { | ||||
|             const LoginBox = sdk.getComponent('structures.LoginBox'); | ||||
|             messageComposer = <LoginBox/>; | ||||
|             messageComposer = <LoginBox />; | ||||
|         } | ||||
| 
 | ||||
|         // TODO: Why aren't we storing the term/scope/count in this format
 | ||||
|  | @ -1709,7 +1712,7 @@ module.exports = React.createClass({ | |||
|         } | ||||
| 
 | ||||
|         if (inCall) { | ||||
|             let zoomButton, voiceMuteButton, videoMuteButton; | ||||
|             let zoomButton; let voiceMuteButton; let videoMuteButton; | ||||
| 
 | ||||
|             if (call.type === "video") { | ||||
|                 zoomButton = ( | ||||
|  |  | |||
|  | @ -72,7 +72,7 @@ module.exports = React.createClass({ | |||
|         function() { | ||||
|             this.props.onSearch(this.refs.search.value); | ||||
|         }, | ||||
|         100 | ||||
|         100, | ||||
|     ), | ||||
| 
 | ||||
|     onToggleCollapse: function(show) { | ||||
|  | @ -80,8 +80,7 @@ module.exports = React.createClass({ | |||
|             dis.dispatch({ | ||||
|                 action: 'show_left_panel', | ||||
|             }); | ||||
|         } | ||||
|         else { | ||||
|         } else { | ||||
|             dis.dispatch({ | ||||
|                 action: 'hide_left_panel', | ||||
|             }); | ||||
|  | @ -103,25 +102,24 @@ module.exports = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     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) { | ||||
|             toggleCollapse = | ||||
|                 <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") }/> | ||||
|                 </AccessibleButton> | ||||
|         } | ||||
|         else { | ||||
|                     <TintableSvg src="img/maximise.svg" width="10" height="16" alt={ _t("Expand panel") } /> | ||||
|                 </AccessibleButton>; | ||||
|         } else { | ||||
|             toggleCollapse = | ||||
|                 <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") }/> | ||||
|                 </AccessibleButton> | ||||
|                     <TintableSvg src="img/minimise.svg" width="10" height="16" alt={ _t("Collapse panel") } /> | ||||
|                 </AccessibleButton>; | ||||
|         } | ||||
| 
 | ||||
|         var searchControls; | ||||
|         let searchControls; | ||||
|         if (!this.props.collapsed) { | ||||
|             searchControls = [ | ||||
|                     this.state.searchTerm.length > 0 ? | ||||
|  | @ -148,16 +146,16 @@ module.exports = React.createClass({ | |||
|                         onChange={ this.onChange } | ||||
|                         onKeyDown={ this._onKeyDown } | ||||
|                         placeholder={ _t('Filter room names') } | ||||
|                     /> | ||||
|                     />, | ||||
|                 ]; | ||||
|         } | ||||
| 
 | ||||
|         var self = this; | ||||
|         const self = this; | ||||
|         return ( | ||||
|             <div className="mx_SearchBox"> | ||||
|                 { searchControls } | ||||
|                 { toggleCollapse } | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -829,7 +829,7 @@ var TimelinePanel = React.createClass({ | |||
|         // 4. Also, if pos === null, the event might not be paginated - show the unread bar
 | ||||
|         const pos = this.getReadMarkerPosition(); | ||||
|         return this.state.readMarkerEventId !== null && // 1.
 | ||||
|             this.state.readMarkerEventId !== this._getCurrentReadReceipt() &&  // 2.
 | ||||
|             this.state.readMarkerEventId !== this._getCurrentReadReceipt() && // 2.
 | ||||
|             (pos < 0 || pos === null); // 3., 4.
 | ||||
|     }, | ||||
| 
 | ||||
|  |  | |||
|  | @ -846,7 +846,7 @@ module.exports = React.createClass({ | |||
|         SettingsStore.getLabsFeatures().forEach((featureId) => { | ||||
|             // TODO: this ought to be a separate component so that we don't need
 | ||||
|             // to rebind the onChange each time we render
 | ||||
|             const onChange = async (e) => { | ||||
|             const onChange = async(e) => { | ||||
|                 const checked = e.target.checked; | ||||
|                 if (featureId === "feature_lazyloading") { | ||||
|                     const confirmed = await this._onLazyLoadChanging(checked); | ||||
|  | @ -1299,7 +1299,7 @@ module.exports = React.createClass({ | |||
|         // 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.
 | ||||
|         let olmVersionString = "<not-enabled>"; | ||||
|         if (olmVersion !== undefined) { | ||||
|         if (olmVersion) { | ||||
|             olmVersionString = `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`; | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -53,5 +53,5 @@ module.exports = React.createClass({ | |||
|                 </SyntaxHighlight> | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -79,7 +79,7 @@ module.exports = React.createClass({ | |||
|         const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); | ||||
| 
 | ||||
|         let {member, fallbackUserId, onClick, viewUserOnClick, ...otherProps} = this.props; | ||||
|         let userId = member ? member.userId : fallbackUserId; | ||||
|         const userId = member ? member.userId : fallbackUserId; | ||||
| 
 | ||||
|         if (viewUserOnClick) { | ||||
|             onClick = () => { | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ export default class GroupInviteTileContextMenu extends React.Component { | |||
|         Modal.createTrackedDialog('Reject community invite', '', QuestionDialog, { | ||||
|             title: _t('Reject invitation'), | ||||
|             description: _t('Are you sure you want to reject the invitation?'), | ||||
|             onFinished: async (shouldLeave) => { | ||||
|             onFinished: async(shouldLeave) => { | ||||
|                 if (!shouldLeave) return; | ||||
| 
 | ||||
|                 // FIXME: controller shouldn't be loading a view :(
 | ||||
|  |  | |||
|  | @ -31,13 +31,13 @@ export default class ChangelogDialog extends React.Component { | |||
|     componentDidMount() { | ||||
|         const version = this.props.newVersion.split('-'); | ||||
|         const version2 = this.props.version.split('-'); | ||||
|         if(version == null || version2 == null) return; | ||||
|         if (version == null || version2 == null) return; | ||||
|         // parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version]
 | ||||
|         for(let i=0; i<REPOS.length; i++) { | ||||
|         for (let i=0; i<REPOS.length; i++) { | ||||
|             const oldVersion = version2[2*i]; | ||||
|             const newVersion = version[2*i]; | ||||
|             request(`https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`, (a, b, body) => { | ||||
|                 if(body == null) return; | ||||
|                 if (body == null) return; | ||||
|                 this.setState({[REPOS[i]]: JSON.parse(body).commits}); | ||||
|             }); | ||||
|         } | ||||
|  | @ -66,7 +66,7 @@ export default class ChangelogDialog extends React.Component { | |||
|                     {this.state[repo].map(this._elementsForCommit)} | ||||
|                     </ul> | ||||
|                 </div> | ||||
|             ) | ||||
|             ); | ||||
|         }); | ||||
| 
 | ||||
|         const content = ( | ||||
|  | @ -83,7 +83,7 @@ export default class ChangelogDialog extends React.Component { | |||
|                 button={_t("Update")} | ||||
|                 onFinished={this.props.onFinished} | ||||
|                 /> | ||||
|         ) | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,7 +26,6 @@ import Unread from '../../../Unread'; | |||
| import classNames from 'classnames'; | ||||
| 
 | ||||
| export default class ChatCreateOrReuseDialog extends React.Component { | ||||
| 
 | ||||
|     constructor(props) { | ||||
|         super(props); | ||||
|         this.onFinished = this.onFinished.bind(this); | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ export default React.createClass({ | |||
|         let error = null; | ||||
|         if (!this.state.groupId) { | ||||
|             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 '=_-./'"); | ||||
|         } | ||||
|         this.setState({ | ||||
|  |  | |||
|  | @ -625,7 +625,7 @@ export default class DevtoolsDialog extends React.Component { | |||
|         let body; | ||||
| 
 | ||||
|         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_right">Room ID: { this.props.roomId }</div> | ||||
|                 <div className="mx_DevTools_label_bottom" /> | ||||
|  | @ -634,7 +634,7 @@ export default class DevtoolsDialog extends React.Component { | |||
|         } else { | ||||
|             const classes = "mx_DevTools_RoomStateExplorer_button"; | ||||
|             body = <div> | ||||
|                 <div className="mx_DevTools_dialog"> | ||||
|                 <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_bottom" /> | ||||
|  |  | |||
|  | @ -153,8 +153,8 @@ export default class NetworkDropdown extends React.Component { | |||
| 
 | ||||
|                         const sortedInstances = this.props.protocols[proto].instances; | ||||
|                         sortedInstances.sort(function(x, y) { | ||||
|                             const a = x.desc | ||||
|                             const b = y.desc | ||||
|                             const a = x.desc; | ||||
|                             const b = y.desc; | ||||
|                             if (a < b) { | ||||
|                                 return -1; | ||||
|                             } 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}> | ||||
|             {icon} | ||||
|             <span className="mx_NetworkDropdown_menu_network">{name}</span> | ||||
|         </div> | ||||
|         </div>; | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|  | @ -223,11 +223,11 @@ export default class NetworkDropdown extends React.Component { | |||
|             current_value = <input type="text" className="mx_NetworkDropdown_networkoption" | ||||
|                 ref={this.collectInputTextBox} onKeyUp={this.onInputKeyUp} | ||||
|                 placeholder="matrix.org" // 'matrix.org' as an example of an HS name
 | ||||
|             /> | ||||
|             />; | ||||
|         } else { | ||||
|             const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId); | ||||
|             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}); | ||||
| 
 | ||||
|                         // 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( | ||||
|                             this.props.room.roomId, | ||||
|                             this.props.id, | ||||
|  |  | |||
|  | @ -78,7 +78,7 @@ export default React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|         let blacklistButton = null, verifyButton = null; | ||||
|         let blacklistButton = null; let verifyButton = null; | ||||
| 
 | ||||
|         if (this.state.device.isBlocked()) { | ||||
|             blacklistButton = ( | ||||
|  |  | |||
|  | @ -122,7 +122,6 @@ export default class EditableTextContainer extends React.Component { | |||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| EditableTextContainer.propTypes = { | ||||
|  |  | |||
|  | @ -16,13 +16,13 @@ limitations under the License. | |||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| var React = require('react'); | ||||
| const React = require('react'); | ||||
| 
 | ||||
| var MatrixClientPeg = require('../../../MatrixClientPeg'); | ||||
| const MatrixClientPeg = require('../../../MatrixClientPeg'); | ||||
| 
 | ||||
| import {formatDate} from '../../../DateUtils'; | ||||
| var filesize = require('filesize'); | ||||
| var AccessibleButton = require('../../../components/views/elements/AccessibleButton'); | ||||
| const filesize = require('filesize'); | ||||
| const AccessibleButton = require('../../../components/views/elements/AccessibleButton'); | ||||
| const Modal = require('../../../Modal'); | ||||
| const sdk = require('../../../index'); | ||||
| import { _t } from '../../../languageHandler'; | ||||
|  | @ -69,24 +69,24 @@ module.exports = React.createClass({ | |||
|         Modal.createTrackedDialog('Confirm Redact Dialog', 'Image View', ConfirmRedactDialog, { | ||||
|             onFinished: (proceed) => { | ||||
|                 if (!proceed) return; | ||||
|                 var self = this; | ||||
|                 const self = this; | ||||
|                 MatrixClientPeg.get().redactEvent( | ||||
|                     this.props.mxEvent.getRoomId(), this.props.mxEvent.getId() | ||||
|                     this.props.mxEvent.getRoomId(), this.props.mxEvent.getId(), | ||||
|                 ).catch(function(e) { | ||||
|                     var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); | ||||
|                     const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); | ||||
|                     // 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, { | ||||
|                         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(); | ||||
|             } | ||||
|             }, | ||||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|     getName: function () { | ||||
|         var name = this.props.name; | ||||
|     getName: function() { | ||||
|         let name = this.props.name; | ||||
|         if (name && this.props.link) { | ||||
|             name = <a href={ this.props.link } target="_blank" rel="noopener">{ name }</a>; | ||||
|         } | ||||
|  | @ -94,7 +94,6 @@ module.exports = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
| 
 | ||||
| /* | ||||
|         // In theory max-width: 80%, max-height: 80% on the CSS should work
 | ||||
|         // but in practice, it doesn't, so do it manually:
 | ||||
|  | @ -123,7 +122,7 @@ module.exports = React.createClass({ | |||
|             height: displayHeight | ||||
|         }; | ||||
| */ | ||||
|         var style, res; | ||||
|         let style; let res; | ||||
| 
 | ||||
|         if (this.props.width && this.props.height) { | ||||
|             style = { | ||||
|  | @ -133,23 +132,22 @@ module.exports = React.createClass({ | |||
|             res = style.width + "x" + style.height + "px"; | ||||
|         } | ||||
| 
 | ||||
|         var size; | ||||
|         let size; | ||||
|         if (this.props.fileSize) { | ||||
|             size = filesize(this.props.fileSize); | ||||
|         } | ||||
| 
 | ||||
|         var size_res; | ||||
|         let size_res; | ||||
|         if (size && res) { | ||||
|             size_res = size + ", " + res; | ||||
|         } | ||||
|         else { | ||||
|         } else { | ||||
|             size_res = size || res; | ||||
|         } | ||||
| 
 | ||||
|         var showEventMeta = !!this.props.mxEvent; | ||||
|         const showEventMeta = !!this.props.mxEvent; | ||||
| 
 | ||||
|         var eventMeta; | ||||
|         if(showEventMeta) { | ||||
|         let eventMeta; | ||||
|         if (showEventMeta) { | ||||
|             // Figure out the sender, defaulting to mxid
 | ||||
|             let sender = this.props.mxEvent.getSender(); | ||||
|             const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); | ||||
|  | @ -163,8 +161,8 @@ module.exports = React.createClass({ | |||
|             </div>); | ||||
|         } | ||||
| 
 | ||||
|         var eventRedact; | ||||
|         if(showEventMeta) { | ||||
|         let eventRedact; | ||||
|         if (showEventMeta) { | ||||
|             eventRedact = (<div className="mx_ImageView_button" onClick={this.onRedactClick}> | ||||
|                 { _t('Remove') } | ||||
|             </div>); | ||||
|  | @ -175,10 +173,10 @@ module.exports = React.createClass({ | |||
|                 <div className="mx_ImageView_lhs"> | ||||
|                 </div> | ||||
|                 <div className="mx_ImageView_content"> | ||||
|                     <img src={this.props.src} style={style}/> | ||||
|                     <img src={this.props.src} style={style} /> | ||||
|                     <div className="mx_ImageView_labelWrapper"> | ||||
|                         <div className="mx_ImageView_label"> | ||||
|                             <AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }><img src="img/cancel-white.svg" width="18" height="18" alt={ _t('Close') }/></AccessibleButton> | ||||
|                             <AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }><img src="img/cancel-white.svg" width="18" height="18" alt={ _t('Close') } /></AccessibleButton> | ||||
|                             <div className="mx_ImageView_shim"> | ||||
|                             </div> | ||||
|                             <div className="mx_ImageView_name"> | ||||
|  | @ -187,7 +185,7 @@ module.exports = React.createClass({ | |||
|                             { eventMeta } | ||||
|                             <a className="mx_ImageView_link" href={ this.props.src } download={ this.props.name } target="_blank" rel="noopener"> | ||||
|                                 <div className="mx_ImageView_download"> | ||||
|                                         { _t('Download this file') }<br/> | ||||
|                                         { _t('Download this file') }<br /> | ||||
|                                          <span className="mx_ImageView_size">{ size_res }</span> | ||||
|                                 </div> | ||||
|                             </a> | ||||
|  | @ -201,5 +199,5 @@ module.exports = React.createClass({ | |||
|                 </div> | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -20,14 +20,14 @@ module.exports = React.createClass({ | |||
|     displayName: 'InlineSpinner', | ||||
| 
 | ||||
|     render: function() { | ||||
|         var w = this.props.w || 16; | ||||
|         var h = this.props.h || 16; | ||||
|         var imgClass = this.props.imgClassName || ""; | ||||
|         const w = this.props.w || 16; | ||||
|         const h = this.props.h || 16; | ||||
|         const imgClass = this.props.imgClassName || ""; | ||||
| 
 | ||||
|         return ( | ||||
|             <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> | ||||
|         ); | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -54,7 +54,6 @@ function getOrCreateContainer(containerId) { | |||
|  * bounding rect as the parent of PE. | ||||
|  */ | ||||
| export default class PersistedElement extends React.Component { | ||||
| 
 | ||||
|     static propTypes = { | ||||
|         // Unique identifier for this PersistedElement instance
 | ||||
|         // 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
 | ||||
| // 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({ | ||||
|     statics: { | ||||
|  |  | |||
|  | @ -16,19 +16,19 @@ limitations under the License. | |||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| var React = require('react'); | ||||
| const React = require('react'); | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'Spinner', | ||||
| 
 | ||||
|     render: function() { | ||||
|         var w = this.props.w || 32; | ||||
|         var h = this.props.h || 32; | ||||
|         var imgClass = this.props.imgClassName || ""; | ||||
|         const w = this.props.w || 32; | ||||
|         const h = this.props.h || 32; | ||||
|         const imgClass = this.props.imgClassName || ""; | ||||
|         return ( | ||||
|             <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> | ||||
|         ); | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -20,7 +20,6 @@ import TintableSvg from './TintableSvg'; | |||
| import AccessibleButton from './AccessibleButton'; | ||||
| 
 | ||||
| export default class TintableSvgButton extends React.Component { | ||||
| 
 | ||||
|     constructor(props) { | ||||
|         super(props); | ||||
|     } | ||||
|  |  | |||
|  | @ -39,7 +39,7 @@ module.exports = React.createClass({ | |||
|                 <div className="mx_MatrixToolbar_content"> | ||||
|                    { _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a> | ||||
|                 </div> | ||||
|                 <AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src="img/cancel.svg" width="18" height="18" alt={_t('Close')}/></AccessibleButton> | ||||
|                 <AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src="img/cancel.svg" width="18" height="18" alt={_t('Close')} /></AccessibleButton> | ||||
|             </div> | ||||
|         ); | ||||
|     }, | ||||
|  |  | |||
|  | @ -45,10 +45,10 @@ export default React.createClass({ | |||
|             description: <div className="mx_MatrixToolbar_changelog">{releaseNotes}</div>, | ||||
|             button: _t("Update"), | ||||
|             onFinished: (update) => { | ||||
|                 if(update && PlatformPeg.get()) { | ||||
|                 if (update && PlatformPeg.get()) { | ||||
|                     PlatformPeg.get().installUpdate(); | ||||
|                 } | ||||
|             } | ||||
|             }, | ||||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|  | @ -58,10 +58,10 @@ export default React.createClass({ | |||
|             version: this.props.version, | ||||
|             newVersion: this.props.newVersion, | ||||
|             onFinished: (update) => { | ||||
|                 if(update && PlatformPeg.get()) { | ||||
|                 if (update && PlatformPeg.get()) { | ||||
|                     PlatformPeg.get().installUpdate(); | ||||
|                 } | ||||
|             } | ||||
|             }, | ||||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|  | @ -103,5 +103,5 @@ export default React.createClass({ | |||
|                 {action_button} | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -32,14 +32,14 @@ export default React.createClass({ | |||
|     getDefaultProps: function() { | ||||
|         return { | ||||
|             detail: '', | ||||
|         } | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     getStatusText: function() { | ||||
|         // we can't import the enum from riot-web as we don't want matrix-react-sdk
 | ||||
|         // to depend on riot-web. so we grab it as a normal object via API instead.
 | ||||
|         const updateCheckStatusEnum = PlatformPeg.get().getUpdateCheckStatusEnum(); | ||||
|         switch(this.props.status) { | ||||
|         switch (this.props.status) { | ||||
|             case updateCheckStatusEnum.ERROR: | ||||
|                 return _t('Error encountered (%(errorDetail)s).', { errorDetail: this.props.detail }); | ||||
|             case updateCheckStatusEnum.CHECKING: | ||||
|  | @ -59,7 +59,7 @@ export default React.createClass({ | |||
|         const message = this.getStatusText(); | ||||
|         const warning = _t('Warning'); | ||||
| 
 | ||||
|         if (!'getUpdateCheckStatusEnum' in PlatformPeg.get()) { | ||||
|         if (!('getUpdateCheckStatusEnum' in PlatformPeg.get())) { | ||||
|             return <div></div>; | ||||
|         } | ||||
| 
 | ||||
|  | @ -83,9 +83,9 @@ export default React.createClass({ | |||
|                     {message} | ||||
|                 </div> | ||||
|                 <AccessibleButton className="mx_MatrixToolbar_close" onClick={this.hideToolbar}> | ||||
|                     <img src="img/cancel.svg" width="18" height="18" alt={_t('Close')}/> | ||||
|                     <img src="img/cancel.svg" width="18" height="18" alt={_t('Close')} /> | ||||
|                 </AccessibleButton> | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
|  |  | |||
|  | @ -296,7 +296,7 @@ export const TermsAuthEntry = React.createClass({ | |||
|             return <Loader />; | ||||
|         } | ||||
| 
 | ||||
|         let checkboxes = []; | ||||
|         const checkboxes = []; | ||||
|         let allChecked = true; | ||||
|         for (const policy of this.state.policies) { | ||||
|             const checked = this.state.toggledPolicies[policy.id]; | ||||
|  | @ -306,7 +306,7 @@ export const TermsAuthEntry = React.createClass({ | |||
|                 <label key={"policy_checkbox_" + policy.id}> | ||||
|                     <input type="checkbox" onClick={() => this._trySubmit(policy.id)} checked={checked} /> | ||||
|                     <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; | ||||
|         } | ||||
| 
 | ||||
|         let newCanonicalAlias = this.state.canonicalAlias; | ||||
|         const newCanonicalAlias = this.state.canonicalAlias; | ||||
| 
 | ||||
|         if (this.props.canSetCanonicalAlias && oldCanonicalAlias !== newCanonicalAlias) { | ||||
|             console.log("AliasSettings: Updating canonical alias"); | ||||
|  | @ -167,7 +167,7 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|         if (!this.props.canonicalAlias) { | ||||
|             this.setState({ | ||||
|                 canonicalAlias: alias | ||||
|                 canonicalAlias: alias, | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ import { _t } from '../../../languageHandler'; | |||
| import Modal from '../../../Modal'; | ||||
| import isEqual from 'lodash/isEqual'; | ||||
| 
 | ||||
| const GROUP_ID_REGEX = /\+\S+\:\S+/; | ||||
| const GROUP_ID_REGEX = /\+\S+:\S+/; | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'RelatedGroupSettings', | ||||
|  |  | |||
|  | @ -33,7 +33,6 @@ import Autocompleter from '../../../autocomplete/Autocompleter'; | |||
| const COMPOSER_SELECTED = 0; | ||||
| 
 | ||||
| export default class Autocomplete extends React.Component { | ||||
| 
 | ||||
|     constructor(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?
 | ||||
|         let image = p["og:image"]; | ||||
|         let imageMaxWidth = 100, imageMaxHeight = 100; | ||||
|         const imageMaxWidth = 100; const imageMaxHeight = 100; | ||||
|         if (image && image.startsWith("mxc://")) { | ||||
|             image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight); | ||||
|         } | ||||
|  |  | |||
|  | @ -712,7 +712,7 @@ module.exports = withMatrixClient(React.createClass({ | |||
| 
 | ||||
|             if (!member || !member.membership || member.membership === 'leave') { | ||||
|                 const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId(); | ||||
|                 const onInviteUserButton = async () => { | ||||
|                 const onInviteUserButton = async() => { | ||||
|                     try { | ||||
|                         await cli.invite(roomId, member.userId); | ||||
|                     } catch (err) { | ||||
|  |  | |||
|  | @ -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); | ||||
|         if (roomIsEncrypted) { | ||||
|             // 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" | ||||
|                              src="img/icon-text-cancel.svg" /> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 </div>; | ||||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|  |  | |||
|  | @ -67,7 +67,7 @@ const EMOJI_UNICODE_TO_SHORTNAME = mapUnicodeToShort(); | |||
| const REGEX_EMOJI_WHITESPACE = new RegExp('(?:^|\\s)(' + asciiRegexp + ')\\s$'); | ||||
| 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 = { | ||||
|     AT_ROOM_PILL: 'ATROOMPILL', | ||||
|  | @ -175,8 +175,8 @@ export default class MessageComposerInput extends React.Component { | |||
|         // see https://github.com/ianstormtaylor/slate/issues/762#issuecomment-304855095
 | ||||
|         this.direction = ''; | ||||
| 
 | ||||
|         this.plainWithMdPills    = new PlainWithPillsSerializer({ pillFormat: 'md' }); | ||||
|         this.plainWithIdPills    = new PlainWithPillsSerializer({ pillFormat: 'id' }); | ||||
|         this.plainWithMdPills = new PlainWithPillsSerializer({ pillFormat: 'md' }); | ||||
|         this.plainWithIdPills = new PlainWithPillsSerializer({ pillFormat: 'id' }); | ||||
|         this.plainWithPlainPills = new PlainWithPillsSerializer({ pillFormat: 'plain' }); | ||||
| 
 | ||||
|         this.md = new Md({ | ||||
|  | @ -544,7 +544,7 @@ export default class MessageComposerInput extends React.Component { | |||
| 
 | ||||
|         if (editorState.startText !== null) { | ||||
|             const text = editorState.startText.text; | ||||
|             const currentStartOffset = editorState.startOffset; | ||||
|             const currentStartOffset = editorState.selection.start.offset; | ||||
| 
 | ||||
|             // Automatic replacement of plaintext emoji to Unicode emoji
 | ||||
|             if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) { | ||||
|  | @ -558,11 +558,11 @@ export default class MessageComposerInput extends React.Component { | |||
| 
 | ||||
|                     const range = Range.create({ | ||||
|                         anchor: { | ||||
|                             key: editorState.selection.startKey, | ||||
|                             key: editorState.startText.key, | ||||
|                             offset: currentStartOffset - emojiMatch[1].length - 1, | ||||
|                         }, | ||||
|                         focus: { | ||||
|                             key: editorState.selection.startKey, | ||||
|                             key: editorState.startText.key, | ||||
|                             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
 | ||||
|         // 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 firstGrandChild = firstChild && firstChild.nodes.get(0); | ||||
|         if (firstChild && firstGrandChild && | ||||
|  | @ -1260,7 +1260,7 @@ export default class MessageComposerInput extends React.Component { | |||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     selectHistory = async (up) => { | ||||
|     selectHistory = async(up) => { | ||||
|         const delta = up ? -1 : 1; | ||||
| 
 | ||||
|         // True if we are not currently selecting history, but composing a message
 | ||||
|  | @ -1308,7 +1308,7 @@ export default class MessageComposerInput extends React.Component { | |||
|         return true; | ||||
|     }; | ||||
| 
 | ||||
|     onTab = async (e) => { | ||||
|     onTab = async(e) => { | ||||
|         this.setState({ | ||||
|             someCompletions: null, | ||||
|         }); | ||||
|  | @ -1330,7 +1330,7 @@ export default class MessageComposerInput extends React.Component { | |||
|         up ? this.autocomplete.onUpArrow() : this.autocomplete.onDownArrow(); | ||||
|     }; | ||||
| 
 | ||||
|     onEscape = async (e) => { | ||||
|     onEscape = async(e) => { | ||||
|         e.preventDefault(); | ||||
|         if (this.autocomplete) { | ||||
|             this.autocomplete.onEscape(e); | ||||
|  | @ -1349,7 +1349,7 @@ export default class MessageComposerInput extends React.Component { | |||
|     /* If passed null, restores the original editor content from state.originalEditorState. | ||||
|      * If passed a non-null displayedCompletion, modifies state.originalEditorState to compute new state.editorState. | ||||
|      */ | ||||
|     setDisplayedCompletion = async (displayedCompletion: ?Completion): boolean => { | ||||
|     setDisplayedCompletion = async(displayedCompletion: ?Completion): boolean => { | ||||
|         const activeEditorState = this.state.originalEditorState || this.state.editorState; | ||||
| 
 | ||||
|         if (displayedCompletion == null) { | ||||
|  |  | |||
|  | @ -93,7 +93,7 @@ module.exports = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|         let joinBlock, previewBlock; | ||||
|         let joinBlock; let previewBlock; | ||||
| 
 | ||||
|         if (this.props.spinner || this.state.busy) { | ||||
|             const Spinner = sdk.getComponent("elements.Spinner"); | ||||
|  |  | |||
|  | @ -657,31 +657,31 @@ module.exports = React.createClass({ | |||
|         const userLevels = powerLevels.users || {}; | ||||
| 
 | ||||
|         const powerLevelDescriptors = { | ||||
|             users_default: { | ||||
|             "users_default": { | ||||
|                 desc: _t('The default role for new room members is'), | ||||
|                 defaultValue: 0, | ||||
|             }, | ||||
|             events_default: { | ||||
|             "events_default": { | ||||
|                 desc: _t('To send messages, you must be a'), | ||||
|                 defaultValue: 0, | ||||
|             }, | ||||
|             invite: { | ||||
|             "invite": { | ||||
|                 desc: _t('To invite users into the room, you must be a'), | ||||
|                 defaultValue: 50, | ||||
|             }, | ||||
|             state_default: { | ||||
|             "state_default": { | ||||
|                 desc: _t('To configure the room, you must be a'), | ||||
|                 defaultValue: 50, | ||||
|             }, | ||||
|             kick: { | ||||
|             "kick": { | ||||
|                 desc: _t('To kick users, you must be a'), | ||||
|                 defaultValue: 50, | ||||
|             }, | ||||
|             ban: { | ||||
|             "ban": { | ||||
|                 desc: _t('To ban users, you must be a'), | ||||
|                 defaultValue: 50, | ||||
|             }, | ||||
|             redact: { | ||||
|             "redact": { | ||||
|                 desc: _t('To remove other users\' messages, you must be a'), | ||||
|                 defaultValue: 50, | ||||
|             }, | ||||
|  |  | |||
|  | @ -67,7 +67,7 @@ module.exports = React.createClass({ | |||
|     phases: { | ||||
|         LOADING: "LOADING", // The component is loading or sending data to the hs
 | ||||
|         DISPLAY: "DISPLAY", // The component is ready and display data
 | ||||
|         ERROR: "ERROR",      // There was an error
 | ||||
|         ERROR: "ERROR", // There was an error
 | ||||
|     }, | ||||
| 
 | ||||
|     propTypes: { | ||||
|  | @ -86,14 +86,14 @@ module.exports = React.createClass({ | |||
|     getInitialState: function() { | ||||
|         return { | ||||
|             phase: this.phases.LOADING, | ||||
|             masterPushRule: undefined,      // The master rule ('.m.rule.master')
 | ||||
|             vectorPushRules: [],            // HS default push rules displayed in Vector UI
 | ||||
|             vectorContentRules: {           // Keyword push rules displayed in Vector UI
 | ||||
|             masterPushRule: undefined, // The master rule ('.m.rule.master')
 | ||||
|             vectorPushRules: [], // HS default push rules displayed in Vector UI
 | ||||
|             vectorContentRules: { // Keyword push rules displayed in Vector UI
 | ||||
|                 vectorState: PushRuleVectorState.ON, | ||||
|                 rules: [], | ||||
|             }, | ||||
|             externalPushRules: [],          // Push rules (except content rule) that have been defined outside Vector UI
 | ||||
|             externalContentRules: [],        // Keyword push rules that have been defined outside Vector UI
 | ||||
|             externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI
 | ||||
|             externalContentRules: [], // Keyword push rules that have been defined outside Vector UI
 | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|  | @ -290,7 +290,7 @@ module.exports = React.createClass({ | |||
|         for (const i in this.state.vectorContentRules.rules) { | ||||
|             const rule = this.state.vectorContentRules.rules[i]; | ||||
| 
 | ||||
|             let enabled, actions; | ||||
|             let enabled; let actions; | ||||
|             switch (newPushRuleVectorState) { | ||||
|                 case PushRuleVectorState.ON: | ||||
|                     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. | ||||
| */ | ||||
| 
 | ||||
| import MatrixClientPeg from "./MatrixClientPeg"; | ||||
| 
 | ||||
| export const host = "matrix.to"; | ||||
| 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) { | ||||
|     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) { | ||||
|  | @ -26,9 +39,98 @@ export function makeUserPermalink(userId) { | |||
| } | ||||
| 
 | ||||
| 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) { | ||||
|     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'; | ||||
| 
 | ||||
| var NotificationUtils = require('./NotificationUtils'); | ||||
| const NotificationUtils = require('./NotificationUtils'); | ||||
| 
 | ||||
| var encodeActions = NotificationUtils.encodeActions; | ||||
| const encodeActions = NotificationUtils.encodeActions; | ||||
| 
 | ||||
| module.exports = { | ||||
|     ACTION_NOTIFY: encodeActions({notify: true}), | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ See the License for the specific language governing permissions and | |||
| 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 | ||||
|  |  | |||
|  | @ -248,7 +248,7 @@ export default class SettingsStore { | |||
|         if (actualValue !== undefined && actualValue !== null) return actualValue; | ||||
|         return calculatedValue; | ||||
|     } | ||||
|     /* eslint-disable valid-jsdoc */    //https://github.com/eslint/eslint/issues/7307
 | ||||
|     /* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307
 | ||||
|     /** | ||||
|      * Sets the value for a setting. The room ID is optional if the setting is not being | ||||
|      * set for a particular room, otherwise it should be supplied. The value may be null | ||||
|  |  | |||
|  | @ -23,7 +23,6 @@ limitations under the License. | |||
|  * intended to handle environmental factors for specific settings. | ||||
|  */ | ||||
| export default class SettingController { | ||||
| 
 | ||||
|     /** | ||||
|      * 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. | ||||
|  |  | |||
|  | @ -23,7 +23,6 @@ import Unread from '../Unread'; | |||
|  * the RoomList. | ||||
|  */ | ||||
| class RoomListStore extends Store { | ||||
| 
 | ||||
|     static _listOrders = { | ||||
|         "m.favourite": "manual", | ||||
|         "im.vector.fake.invite": "recent", | ||||
|  |  | |||
|  | @ -93,7 +93,7 @@ export default class DMRoomMap { | |||
|                         return {userId, roomId}; | ||||
|                     } | ||||
|                 } | ||||
|             }).filter((ids) => !!ids);  //filter out
 | ||||
|             }).filter((ids) => !!ids); //filter out
 | ||||
|             // these are actually all legit self-chats
 | ||||
|             // bail out
 | ||||
|             if (!guessedUserIdsThatChanged.length) { | ||||
|  |  | |||
|  | @ -77,7 +77,7 @@ const ALLOWED_BLOB_MIMETYPES = { | |||
|     'audio/x-pn-wav': true, | ||||
|     'audio/flac': true, | ||||
|     'audio/x-flac': true, | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Decrypt a file attached to a matrix event. | ||||
|  |  | |||
|  | @ -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 New Issue
	
	 Bruno Windels
						Bruno Windels