Merge branch 'new_release_process_stable' into develop
This reverts all PRs currently marked notready, changing develop into a branch that should be more stable.pull/21833/head
						commit
						8ce6da1b16
					
				|  | @ -65,12 +65,11 @@ | |||
|     "lodash": "^4.13.1", | ||||
|     "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", | ||||
|     "optimist": "^0.6.1", | ||||
|     "prop-types": "^15.5.8", | ||||
|     "q": "^1.4.1", | ||||
|     "react": "^15.4.0", | ||||
|     "react-addons-css-transition-group": "15.3.2", | ||||
|     "react-dom": "^15.4.0", | ||||
|     "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#39d858c", | ||||
|     "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef", | ||||
|     "sanitize-html": "^1.11.1", | ||||
|     "text-encoding-utf-8": "^1.0.1", | ||||
|     "velocity-vector": "vector-im/velocity#059e3b2", | ||||
|  |  | |||
|  | @ -1,62 +0,0 @@ | |||
| /* | ||||
| Copyright 2017 Vector Creations Ltd | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| // singleton which dispatches invocations of a given type & argument
 | ||||
| // rather than just a type (as per EventEmitter and Flux's dispatcher etc)
 | ||||
| //
 | ||||
| // This means you can have a single point which listens for an EventEmitter event
 | ||||
| // and then dispatches out to one of thousands of RoomTiles (for instance) rather than
 | ||||
| // having each RoomTile register for the EventEmitter event and having to 
 | ||||
| // iterate over all of them.
 | ||||
| class ConstantTimeDispatcher { | ||||
|     constructor() { | ||||
|         // type -> arg -> [ listener(arg, params) ]
 | ||||
|         this.listeners = {}; | ||||
|     } | ||||
| 
 | ||||
|     register(type, arg, listener) { | ||||
|         if (!this.listeners[type]) this.listeners[type] = {}; | ||||
|         if (!this.listeners[type][arg]) this.listeners[type][arg] = []; | ||||
|         this.listeners[type][arg].push(listener); | ||||
|     } | ||||
| 
 | ||||
|     unregister(type, arg, listener) { | ||||
|         if (this.listeners[type] && this.listeners[type][arg]) { | ||||
|             var i = this.listeners[type][arg].indexOf(listener); | ||||
|             if (i > -1) { | ||||
|                 this.listeners[type][arg].splice(i, 1); | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             console.warn("Unregistering unrecognised listener (type=" + type + ", arg=" + arg + ")"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     dispatch(type, arg, params) { | ||||
|         if (!this.listeners[type] || !this.listeners[type][arg]) { | ||||
|             //console.warn("No registered listeners for dispatch (type=" + type + ", arg=" + arg + ")");
 | ||||
|             return; | ||||
|         } | ||||
|         this.listeners[type][arg].forEach(listener=>{ | ||||
|             listener.call(arg, params); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| if (!global.constantTimeDispatcher) { | ||||
|     global.constantTimeDispatcher = new ConstantTimeDispatcher(); | ||||
| } | ||||
| module.exports = global.constantTimeDispatcher; | ||||
|  | @ -32,5 +32,4 @@ module.exports = { | |||
|     DELETE: 46, | ||||
|     KEY_D: 68, | ||||
|     KEY_E: 69, | ||||
|     KEY_K: 75, | ||||
| }; | ||||
|  |  | |||
|  | @ -107,18 +107,6 @@ export default React.createClass({ | |||
|         var handled = false; | ||||
| 
 | ||||
|         switch (ev.keyCode) { | ||||
|             case KeyCode.ESCAPE: | ||||
| 
 | ||||
|                 // Implemented this way so possible handling for other pages is neater
 | ||||
|                 switch (this.props.page_type) { | ||||
|                     case PageTypes.UserSettings: | ||||
|                         this.props.onUserSettingsClose(); | ||||
|                         handled = true; | ||||
|                         break; | ||||
|                 } | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case KeyCode.UP: | ||||
|             case KeyCode.DOWN: | ||||
|                 if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) { | ||||
|  |  | |||
|  | @ -590,7 +590,6 @@ var TimelinePanel = React.createClass({ | |||
|                 this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0); | ||||
|                 dis.dispatch({ | ||||
|                     action: 'on_room_read', | ||||
|                     room: this.props.timelineSet.room, | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -47,35 +47,18 @@ export default React.createClass({ | |||
|         children: React.PropTypes.node, | ||||
|     }, | ||||
| 
 | ||||
|     componentWillMount: function() { | ||||
|         this.priorActiveElement = document.activeElement; | ||||
|     }, | ||||
| 
 | ||||
|     componentWillUnmount: function() { | ||||
|         if (this.priorActiveElement !== null) { | ||||
|             this.priorActiveElement.focus(); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     // Don't let key{down,press} events escape the modal. Consume them all.
 | ||||
|     _eatKeyEvent: function(e) { | ||||
|         e.stopPropagation(); | ||||
|     }, | ||||
|      | ||||
|     // Must be when the key is released (and not pressed) otherwise componentWillUnmount
 | ||||
|     // will focus another element which will receive future key events
 | ||||
|     _onKeyUp: function(e) { | ||||
|     _onKeyDown: function(e) { | ||||
|         if (e.keyCode === KeyCode.ESCAPE) { | ||||
|             e.stopPropagation(); | ||||
|             e.preventDefault(); | ||||
|             this.props.onFinished(); | ||||
|         } else if (e.keyCode === KeyCode.ENTER) { | ||||
|             if (this.props.onEnterPressed) { | ||||
|                 e.stopPropagation(); | ||||
|                 e.preventDefault(); | ||||
|                 this.props.onEnterPressed(e); | ||||
|             } | ||||
|         } | ||||
|         // Consume all keyup events while Modal is open
 | ||||
|         e.stopPropagation(); | ||||
|     }, | ||||
| 
 | ||||
|     _onCancelClick: function(e) { | ||||
|  | @ -84,13 +67,9 @@ export default React.createClass({ | |||
| 
 | ||||
|     render: function() { | ||||
|         const TintableSvg = sdk.getComponent("elements.TintableSvg"); | ||||
| 
 | ||||
|                  | ||||
|         return ( | ||||
|             <div onKeyUp={this._onKeyUp} | ||||
|                  onKeyDown={this._eatKeyEvent} | ||||
|                  onKeyPress={this._eatKeyEvent} | ||||
|                  className={this.props.className} | ||||
|             > | ||||
|             <div onKeyDown={this._onKeyDown} className={this.props.className}> | ||||
|                 <AccessibleButton onClick={this._onCancelClick} | ||||
|                     className="mx_Dialog_cancelButton" | ||||
|                 > | ||||
|  |  | |||
|  | @ -47,12 +47,6 @@ export default React.createClass({ | |||
|         this.props.onFinished(false); | ||||
|     }, | ||||
| 
 | ||||
|     componentDidMount: function() { | ||||
|         if (this.props.focus) { | ||||
|             this.refs.button.focus(); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|         const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); | ||||
|         const cancelButton = this.props.hasCancelButton ? ( | ||||
|  | @ -69,7 +63,7 @@ export default React.createClass({ | |||
|                     {this.props.description} | ||||
|                 </div> | ||||
|                 <div className="mx_Dialog_buttons"> | ||||
|                     <button ref="button" className="mx_Dialog_primary" onClick={this.onOk}> | ||||
|                     <button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}> | ||||
|                         {this.props.button} | ||||
|                     </button> | ||||
|                     {this.props.extraButtons} | ||||
|  |  | |||
|  | @ -1,80 +0,0 @@ | |||
| /* | ||||
| Copyright 2017 Vector Creations Ltd | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import AccessibleButton from './AccessibleButton'; | ||||
| import dis from '../../../dispatcher'; | ||||
| import sdk from '../../../index'; | ||||
| 
 | ||||
| export default React.createClass({ | ||||
|     displayName: 'RoleButton', | ||||
| 
 | ||||
|     propTypes: { | ||||
|         size: PropTypes.string, | ||||
|         tooltip: PropTypes.bool, | ||||
|         action: PropTypes.string.isRequired, | ||||
|         label: PropTypes.string.isRequired, | ||||
|         iconPath: PropTypes.string.isRequired, | ||||
|     }, | ||||
| 
 | ||||
|     getDefaultProps: function() { | ||||
|         return { | ||||
|             size: "25", | ||||
|             tooltip: false, | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     getInitialState: function() { | ||||
|         return { | ||||
|             showTooltip: false, | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     _onClick: function(ev) { | ||||
|         ev.stopPropagation(); | ||||
|         dis.dispatch({action: this.props.action}); | ||||
|     }, | ||||
| 
 | ||||
|     _onMouseEnter: function() { | ||||
|         if (this.props.tooltip) this.setState({showTooltip: true}); | ||||
|     }, | ||||
| 
 | ||||
|     _onMouseLeave: function() { | ||||
|         this.setState({showTooltip: false}); | ||||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|         const TintableSvg = sdk.getComponent("elements.TintableSvg"); | ||||
| 
 | ||||
|         let tooltip; | ||||
|         if (this.state.showTooltip) { | ||||
|             const RoomTooltip = sdk.getComponent("rooms.RoomTooltip"); | ||||
|             tooltip = <RoomTooltip className="mx_RoleButton_tooltip" label={this.props.label} />; | ||||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <AccessibleButton className="mx_RoleButton" | ||||
|                 onClick={this._onClick} | ||||
|                 onMouseEnter={this._onMouseEnter} | ||||
|                 onMouseLeave={this._onMouseLeave} | ||||
|             > | ||||
|                 <TintableSvg src={this.props.iconPath} width={this.props.size} height={this.props.size} /> | ||||
|                 {tooltip} | ||||
|             </AccessibleButton> | ||||
|         ); | ||||
|     } | ||||
| }); | ||||
|  | @ -1,38 +0,0 @@ | |||
| /* | ||||
| Copyright 2017 Vector Creations Ltd | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import sdk from '../../../index'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const CreateRoomButton = function(props) { | ||||
|     const ActionButton = sdk.getComponent('elements.ActionButton'); | ||||
|     return ( | ||||
|         <ActionButton action="view_create_room" | ||||
|             label="Create new room" | ||||
|             iconPath="img/icons-create-room.svg" | ||||
|             size={props.size} | ||||
|             tooltip={props.tooltip} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| CreateRoomButton.propTypes = { | ||||
|     size: PropTypes.string, | ||||
|     tooltip: PropTypes.bool, | ||||
| }; | ||||
| 
 | ||||
| export default CreateRoomButton; | ||||
|  | @ -1,38 +0,0 @@ | |||
| /* | ||||
| Copyright 2017 Vector Creations Ltd | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import sdk from '../../../index'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const HomeButton = function(props) { | ||||
|     const ActionButton = sdk.getComponent('elements.ActionButton'); | ||||
|     return ( | ||||
|         <ActionButton action="view_home_page" | ||||
|             label="Welcome page" | ||||
|             iconPath="img/icons-home.svg" | ||||
|             size={props.size} | ||||
|             tooltip={props.tooltip} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| HomeButton.propTypes = { | ||||
|     size: PropTypes.string, | ||||
|     tooltip: PropTypes.bool, | ||||
| }; | ||||
| 
 | ||||
| export default HomeButton; | ||||
|  | @ -1,38 +0,0 @@ | |||
| /* | ||||
| Copyright 2017 Vector Creations Ltd | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import sdk from '../../../index'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const RoomDirectoryButton = function(props) { | ||||
|     const ActionButton = sdk.getComponent('elements.ActionButton'); | ||||
|     return ( | ||||
|         <ActionButton action="view_room_directory" | ||||
|             label="Room directory" | ||||
|             iconPath="img/icons-directory.svg" | ||||
|             size={props.size} | ||||
|             tooltip={props.tooltip} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| RoomDirectoryButton.propTypes = { | ||||
|     size: PropTypes.string, | ||||
|     tooltip: PropTypes.bool, | ||||
| }; | ||||
| 
 | ||||
| export default RoomDirectoryButton; | ||||
|  | @ -1,38 +0,0 @@ | |||
| /* | ||||
| Copyright 2017 Vector Creations Ltd | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import sdk from '../../../index'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const SettingsButton = function(props) { | ||||
|     const ActionButton = sdk.getComponent('elements.ActionButton'); | ||||
|     return ( | ||||
|         <ActionButton action="view_user_settings" | ||||
|             label="Settings" | ||||
|             iconPath="img/icons-settings.svg" | ||||
|             size={props.size} | ||||
|             tooltip={props.tooltip} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| SettingsButton.propTypes = { | ||||
|     size: PropTypes.string, | ||||
|     tooltip: PropTypes.bool, | ||||
| }; | ||||
| 
 | ||||
| export default SettingsButton; | ||||
|  | @ -1,38 +0,0 @@ | |||
| /* | ||||
| Copyright 2017 Vector Creations Ltd | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import sdk from '../../../index'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const StartChatButton = function(props) { | ||||
|     const ActionButton = sdk.getComponent('elements.ActionButton'); | ||||
|     return ( | ||||
|         <ActionButton action="view_create_chat" | ||||
|             label="Start chat" | ||||
|             iconPath="img/icons-people.svg" | ||||
|             size={props.size} | ||||
|             tooltip={props.tooltip} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| StartChatButton.propTypes = { | ||||
|     size: PropTypes.string, | ||||
|     tooltip: PropTypes.bool, | ||||
| }; | ||||
| 
 | ||||
| export default StartChatButton; | ||||
|  | @ -1,6 +1,5 @@ | |||
| /* | ||||
| Copyright 2015, 2016 OpenMarket 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,23 +21,15 @@ var GeminiScrollbar = require('react-gemini-scrollbar'); | |||
| var MatrixClientPeg = require("../../../MatrixClientPeg"); | ||||
| var CallHandler = require('../../../CallHandler'); | ||||
| var RoomListSorter = require("../../../RoomListSorter"); | ||||
| var Unread = require('../../../Unread'); | ||||
| var dis = require("../../../dispatcher"); | ||||
| var sdk = require('../../../index'); | ||||
| var rate_limited_func = require('../../../ratelimitedfunc'); | ||||
| var Rooms = require('../../../Rooms'); | ||||
| import DMRoomMap from '../../../utils/DMRoomMap'; | ||||
| var Receipt = require('../../../utils/Receipt'); | ||||
| var constantTimeDispatcher = require('../../../ConstantTimeDispatcher'); | ||||
| import AccessibleButton from '../elements/AccessibleButton'; | ||||
| 
 | ||||
| const HIDE_CONFERENCE_CHANS = true; | ||||
| 
 | ||||
| const VERBS = { | ||||
|     'm.favourite': 'favourite', | ||||
|     'im.vector.fake.direct': 'tag direct chat', | ||||
|     'im.vector.fake.recent': 'restore', | ||||
|     'm.lowpriority': 'demote', | ||||
| }; | ||||
| var HIDE_CONFERENCE_CHANS = true; | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'RoomList', | ||||
|  | @ -46,23 +37,13 @@ module.exports = React.createClass({ | |||
|     propTypes: { | ||||
|         ConferenceHandler: React.PropTypes.any, | ||||
|         collapsed: React.PropTypes.bool.isRequired, | ||||
|         selectedRoom: React.PropTypes.string, | ||||
|         currentRoom: React.PropTypes.string, | ||||
|         searchFilter: React.PropTypes.string, | ||||
|     }, | ||||
| 
 | ||||
|     shouldComponentUpdate: function(nextProps, nextState) { | ||||
|         if (nextProps.collapsed !== this.props.collapsed) return true; | ||||
|         if (nextProps.searchFilter !== this.props.searchFilter) return true; | ||||
|         if (nextState.lists !== this.state.lists || | ||||
|             nextState.isLoadingLeftRooms !== this.state.isLoadingLeftRooms || | ||||
|             nextState.incomingCall !== this.state.incomingCall) return true; | ||||
|         return false; | ||||
|     }, | ||||
| 
 | ||||
|     getInitialState: function() { | ||||
|         return { | ||||
|             isLoadingLeftRooms: false, | ||||
|             totalRoomCount: null, | ||||
|             lists: {}, | ||||
|             incomingCall: null, | ||||
|         }; | ||||
|  | @ -76,21 +57,12 @@ module.exports = React.createClass({ | |||
|         cli.on("Room.name", this.onRoomName); | ||||
|         cli.on("Room.tags", this.onRoomTags); | ||||
|         cli.on("Room.receipt", this.onRoomReceipt); | ||||
|         cli.on("RoomState.members", this.onRoomStateMember); | ||||
|         cli.on("RoomState.events", this.onRoomStateEvents); | ||||
|         cli.on("RoomMember.name", this.onRoomMemberName); | ||||
|         cli.on("accountData", this.onAccountData); | ||||
| 
 | ||||
|         // lookup for which lists a given roomId is currently in.
 | ||||
|         this.listsForRoomId = {}; | ||||
| 
 | ||||
|         this.refreshRoomList(); | ||||
| 
 | ||||
|         // order of the sublists
 | ||||
|         //this.listOrder = [];
 | ||||
| 
 | ||||
|         // loop count to stop a stack overflow if the user keeps waggling the
 | ||||
|         // mouse for >30s in a row, or if running under mocha
 | ||||
|         this._delayedRefreshRoomListLoopCount = 0 | ||||
|         var s = this.getRoomLists(); | ||||
|         this.setState(s); | ||||
|     }, | ||||
| 
 | ||||
|     componentDidMount: function() { | ||||
|  | @ -99,22 +71,7 @@ module.exports = React.createClass({ | |||
|         this._updateStickyHeaders(true); | ||||
|     }, | ||||
| 
 | ||||
|     componentWillReceiveProps: function(nextProps) { | ||||
|         // short-circuit react when the room changes
 | ||||
|         // to avoid rerendering all the sublists everywhere
 | ||||
|         if (nextProps.selectedRoom !== this.props.selectedRoom) { | ||||
|             if (this.props.selectedRoom) { | ||||
|                 constantTimeDispatcher.dispatch( | ||||
|                     "RoomTile.select", this.props.selectedRoom, {} | ||||
|                 ); | ||||
|             } | ||||
|             constantTimeDispatcher.dispatch( | ||||
|                 "RoomTile.select", nextProps.selectedRoom, { selected: true } | ||||
|             ); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     componentDidUpdate: function(prevProps, prevState) { | ||||
|     componentDidUpdate: function() { | ||||
|         // Reinitialise the stickyHeaders when the component is updated
 | ||||
|         this._updateStickyHeaders(true); | ||||
|         this._repositionIncomingCallBox(undefined, false); | ||||
|  | @ -140,24 +97,10 @@ module.exports = React.createClass({ | |||
|                 } | ||||
|                 break; | ||||
|             case 'on_room_read': | ||||
|                 // poke the right RoomTile to refresh, using the constantTimeDispatcher
 | ||||
|                 // to avoid each and every RoomTile registering to the 'on_room_read' event
 | ||||
|                 // XXX: if we like the constantTimeDispatcher we might want to dispatch
 | ||||
|                 // directly from TimelinePanel rather than needlessly bouncing via here.
 | ||||
|                 constantTimeDispatcher.dispatch( | ||||
|                     "RoomTile.refresh", payload.room.roomId, {} | ||||
|                 ); | ||||
| 
 | ||||
|                 // also have to poke the right list(s)
 | ||||
|                 var lists = this.listsForRoomId[payload.room.roomId]; | ||||
|                 if (lists) { | ||||
|                     lists.forEach(list=>{ | ||||
|                         constantTimeDispatcher.dispatch( | ||||
|                             "RoomSubList.refreshHeader", list, { room: payload.room } | ||||
|                         ); | ||||
|                     }); | ||||
|                 } | ||||
| 
 | ||||
|                 // Force an update because the notif count state is too deep to cause
 | ||||
|                 // an update. This forces the local echo of reading notifs to be
 | ||||
|                 // reflected by the RoomTiles.
 | ||||
|                 this.forceUpdate(); | ||||
|                 break; | ||||
|         } | ||||
|     }, | ||||
|  | @ -171,7 +114,7 @@ module.exports = React.createClass({ | |||
|             MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); | ||||
|             MatrixClientPeg.get().removeListener("Room.tags", this.onRoomTags); | ||||
|             MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt); | ||||
|             MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember); | ||||
|             MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); | ||||
|             MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName); | ||||
|             MatrixClientPeg.get().removeListener("accountData", this.onAccountData); | ||||
|         } | ||||
|  | @ -180,14 +123,10 @@ module.exports = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     onRoom: function(room) { | ||||
|         // XXX: this happens rarely; ideally we should only update the correct
 | ||||
|         // sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
 | ||||
|         this._delayedRefreshRoomList(); | ||||
|     }, | ||||
| 
 | ||||
|     onDeleteRoom: function(roomId) { | ||||
|         // XXX: this happens rarely; ideally we should only update the correct
 | ||||
|         // sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
 | ||||
|         this._delayedRefreshRoomList(); | ||||
|     }, | ||||
| 
 | ||||
|  | @ -210,10 +149,6 @@ module.exports = React.createClass({ | |||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     _onMouseOver: function(ev) { | ||||
|         this._lastMouseOverTs = Date.now(); | ||||
|     }, | ||||
| 
 | ||||
|     onSubListHeaderClick: function(isHidden, scrollToPosition) { | ||||
|         // The scroll area has expanded or contracted, so re-calculate sticky headers positions
 | ||||
|         this._updateStickyHeaders(true, scrollToPosition); | ||||
|  | @ -223,98 +158,41 @@ module.exports = React.createClass({ | |||
|         if (toStartOfTimeline) return; | ||||
|         if (!room) return; | ||||
|         if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return; | ||||
| 
 | ||||
|         // rather than regenerate our full roomlists, which is very heavy, we poke the
 | ||||
|         // correct sublists to just re-sort themselves.  This isn't enormously reacty,
 | ||||
|         // but is much faster than the default react reconciler, or having to do voodoo
 | ||||
|         // with shouldComponentUpdate and a pleaseRefresh property or similar.
 | ||||
|         var lists = this.listsForRoomId[room.roomId]; | ||||
|         if (lists) { | ||||
|             lists.forEach(list=>{ | ||||
|                 constantTimeDispatcher.dispatch("RoomSubList.sort", list, { room: room }); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         // we have to explicitly hit the roomtile which just changed
 | ||||
|         constantTimeDispatcher.dispatch( | ||||
|             "RoomTile.refresh", room.roomId, {} | ||||
|         ); | ||||
|         this._delayedRefreshRoomList(); | ||||
|     }, | ||||
| 
 | ||||
|     onRoomReceipt: function(receiptEvent, room) { | ||||
|         // because if we read a notification, it will affect notification count
 | ||||
|         // only bother updating if there's a receipt from us
 | ||||
|         if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) { | ||||
|             var lists = this.listsForRoomId[room.roomId]; | ||||
|             if (lists) { | ||||
|                 lists.forEach(list=>{ | ||||
|                     constantTimeDispatcher.dispatch( | ||||
|                         "RoomSubList.refreshHeader", list, { room: room } | ||||
|                     ); | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             // we have to explicitly hit the roomtile which just changed
 | ||||
|             constantTimeDispatcher.dispatch( | ||||
|                 "RoomTile.refresh", room.roomId, {} | ||||
|             ); | ||||
|             this._delayedRefreshRoomList(); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     onRoomName: function(room) { | ||||
|         constantTimeDispatcher.dispatch( | ||||
|             "RoomTile.refresh", room.roomId, {} | ||||
|         ); | ||||
|     }, | ||||
| 
 | ||||
|     onRoomTags: function(event, room) { | ||||
|         // XXX: this happens rarely; ideally we should only update the correct
 | ||||
|         // sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
 | ||||
|         this._delayedRefreshRoomList(); | ||||
|     }, | ||||
| 
 | ||||
|     onRoomStateMember: function(ev, state, member) { | ||||
|         if (ev.getStateKey() === MatrixClientPeg.get().credentials.userId && | ||||
|             ev.getPrevContent() && ev.getPrevContent().membership === "invite") | ||||
|         { | ||||
|             this._delayedRefreshRoomList(); | ||||
|         } | ||||
|         else { | ||||
|             constantTimeDispatcher.dispatch( | ||||
|                 "RoomTile.refresh", member.roomId, {} | ||||
|             ); | ||||
|         } | ||||
|     onRoomTags: function(event, room) { | ||||
|         this._delayedRefreshRoomList(); | ||||
|     }, | ||||
| 
 | ||||
|     onRoomStateEvents: function(ev, state) { | ||||
|         this._delayedRefreshRoomList(); | ||||
|     }, | ||||
| 
 | ||||
|     onRoomMemberName: function(ev, member) { | ||||
|         constantTimeDispatcher.dispatch( | ||||
|             "RoomTile.refresh", member.roomId, {} | ||||
|         ); | ||||
|         this._delayedRefreshRoomList(); | ||||
|     }, | ||||
| 
 | ||||
|     onAccountData: function(ev) { | ||||
|         if (ev.getType() == 'm.direct') { | ||||
|             // XXX: this happens rarely; ideally we should only update the correct
 | ||||
|             // sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
 | ||||
|             this._delayedRefreshRoomList(); | ||||
|         } | ||||
|         else if (ev.getType() == 'm.push_rules') { | ||||
|             this._delayedRefreshRoomList(); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     _delayedRefreshRoomList: new rate_limited_func(function() { | ||||
|         // if the mouse has been moving over the RoomList in the last 500ms
 | ||||
|         // then delay the refresh further to avoid bouncing around under the
 | ||||
|         // cursor
 | ||||
|         if (Date.now() - this._lastMouseOverTs > 500 || this._delayedRefreshRoomListLoopCount > 60) { | ||||
|             this.refreshRoomList(); | ||||
|             this._delayedRefreshRoomListLoopCount = 0; | ||||
|         } | ||||
|         else { | ||||
|             this._delayedRefreshRoomListLoopCount++; | ||||
|             this._delayedRefreshRoomList(); | ||||
|         } | ||||
|         this.refreshRoomList(); | ||||
|     }, 500), | ||||
| 
 | ||||
|     refreshRoomList: function() { | ||||
|  | @ -322,36 +200,27 @@ module.exports = React.createClass({ | |||
|         //     (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs))
 | ||||
|         // );
 | ||||
| 
 | ||||
|         // TODO: ideally we'd calculate this once at start, and then maintain
 | ||||
|         // any changes to it incrementally, updating the appropriate sublists
 | ||||
|         // as needed.
 | ||||
|         // Alternatively we'd do something magical with Immutable.js or similar.
 | ||||
|         const lists = this.getRoomLists(); | ||||
|         let totalRooms = 0; | ||||
|         for (const l of Object.values(lists)) { | ||||
|             totalRooms += l.length; | ||||
|         } | ||||
|         this.setState({ | ||||
|             lists: this.getRoomLists(), | ||||
|             totalRoomCount: totalRooms, | ||||
|         }); | ||||
|          | ||||
|         // TODO: rather than bluntly regenerating and re-sorting everything
 | ||||
|         // every time we see any kind of room change from the JS SDK
 | ||||
|         // we could do incremental updates on our copy of the state
 | ||||
|         // based on the room which has actually changed.  This would stop
 | ||||
|         // us re-rendering all the sublists every time anything changes anywhere
 | ||||
|         // in the state of the client.
 | ||||
|         this.setState(this.getRoomLists()); | ||||
| 
 | ||||
|         // this._lastRefreshRoomListTs = Date.now();
 | ||||
|     }, | ||||
| 
 | ||||
|     getRoomLists: function() { | ||||
|         var self = this; | ||||
|         const lists = {}; | ||||
|         var s = { lists: {} }; | ||||
| 
 | ||||
|         lists["im.vector.fake.invite"] = []; | ||||
|         lists["m.favourite"] = []; | ||||
|         lists["im.vector.fake.recent"] = []; | ||||
|         lists["im.vector.fake.direct"] = []; | ||||
|         lists["m.lowpriority"] = []; | ||||
|         lists["im.vector.fake.archived"] = []; | ||||
| 
 | ||||
|         this.listsForRoomId = {}; | ||||
|         var otherTagNames = {}; | ||||
|         s.lists["im.vector.fake.invite"] = []; | ||||
|         s.lists["m.favourite"] = []; | ||||
|         s.lists["im.vector.fake.recent"] = []; | ||||
|         s.lists["im.vector.fake.direct"] = []; | ||||
|         s.lists["m.lowpriority"] = []; | ||||
|         s.lists["im.vector.fake.archived"] = []; | ||||
| 
 | ||||
|         const dmRoomMap = new DMRoomMap(MatrixClientPeg.get()); | ||||
| 
 | ||||
|  | @ -364,13 +233,8 @@ module.exports = React.createClass({ | |||
|             //             ", target = " + me.events.member.getStateKey() +
 | ||||
|             //             ", prevMembership = " + me.events.member.getPrevContent().membership);
 | ||||
| 
 | ||||
|             if (!self.listsForRoomId[room.roomId]) { | ||||
|                 self.listsForRoomId[room.roomId] = []; | ||||
|             } | ||||
| 
 | ||||
|             if (me.membership == "invite") { | ||||
|                 self.listsForRoomId[room.roomId].push("im.vector.fake.invite"); | ||||
|                 lists["im.vector.fake.invite"].push(room); | ||||
|                 s.lists["im.vector.fake.invite"].push(room); | ||||
|             } | ||||
|             else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) { | ||||
|                 // skip past this room & don't put it in any lists
 | ||||
|  | @ -380,52 +244,70 @@ module.exports = React.createClass({ | |||
|             { | ||||
|                 // Used to split rooms via tags
 | ||||
|                 var tagNames = Object.keys(room.tags); | ||||
| 
 | ||||
|                 if (tagNames.length) { | ||||
|                     for (var i = 0; i < tagNames.length; i++) { | ||||
|                         var tagName = tagNames[i]; | ||||
|                         lists[tagName] = lists[tagName] || []; | ||||
|                         lists[tagName].push(room); | ||||
|                         self.listsForRoomId[room.roomId].push(tagName); | ||||
|                         otherTagNames[tagName] = 1; | ||||
|                         s.lists[tagName] = s.lists[tagName] || []; | ||||
|                         s.lists[tagNames[i]].push(room); | ||||
|                     } | ||||
|                 } | ||||
|                 else if (dmRoomMap.getUserIdForRoomId(room.roomId)) { | ||||
|                     // "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
 | ||||
|                     self.listsForRoomId[room.roomId].push("im.vector.fake.direct"); | ||||
|                     lists["im.vector.fake.direct"].push(room); | ||||
|                     s.lists["im.vector.fake.direct"].push(room); | ||||
|                 } | ||||
|                 else { | ||||
|                     self.listsForRoomId[room.roomId].push("im.vector.fake.recent"); | ||||
|                     lists["im.vector.fake.recent"].push(room); | ||||
|                     s.lists["im.vector.fake.recent"].push(room); | ||||
|                 } | ||||
|             } | ||||
|             else if (me.membership === "leave") { | ||||
|                 self.listsForRoomId[room.roomId].push("im.vector.fake.archived"); | ||||
|                 lists["im.vector.fake.archived"].push(room); | ||||
|                 s.lists["im.vector.fake.archived"].push(room); | ||||
|             } | ||||
|             else { | ||||
|                 console.error("unrecognised membership: " + me.membership + " - this should never happen"); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         if (s.lists["im.vector.fake.direct"].length == 0 && | ||||
|             MatrixClientPeg.get().getAccountData('m.direct') === undefined && | ||||
|             !MatrixClientPeg.get().isGuest()) | ||||
|         { | ||||
|             // scan through the 'recents' list for any rooms which look like DM rooms
 | ||||
|             // and make them DM rooms
 | ||||
|             const oldRecents = s.lists["im.vector.fake.recent"]; | ||||
|             s.lists["im.vector.fake.recent"] = []; | ||||
| 
 | ||||
|             for (const room of oldRecents) { | ||||
|                 const me = room.getMember(MatrixClientPeg.get().credentials.userId); | ||||
| 
 | ||||
|                 if (me && Rooms.looksLikeDirectMessageRoom(room, me)) { | ||||
|                     s.lists["im.vector.fake.direct"].push(room); | ||||
|                 } else { | ||||
|                     s.lists["im.vector.fake.recent"].push(room); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // save these new guessed DM rooms into the account data
 | ||||
|             const newMDirectEvent = {}; | ||||
|             for (const room of s.lists["im.vector.fake.direct"]) { | ||||
|                 const me = room.getMember(MatrixClientPeg.get().credentials.userId); | ||||
|                 const otherPerson = Rooms.getOnlyOtherMember(room, me); | ||||
|                 if (!otherPerson) continue; | ||||
| 
 | ||||
|                 const roomList = newMDirectEvent[otherPerson.userId] || []; | ||||
|                 roomList.push(room.roomId); | ||||
|                 newMDirectEvent[otherPerson.userId] = roomList; | ||||
|             } | ||||
| 
 | ||||
|             // if this fails, fine, we'll just do the same thing next time we get the room lists
 | ||||
|             MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done(); | ||||
|         } | ||||
| 
 | ||||
|         //console.log("calculated new roomLists; im.vector.fake.recent = " + s.lists["im.vector.fake.recent"]);
 | ||||
| 
 | ||||
|         // we actually apply the sorting to this when receiving the prop in RoomSubLists.
 | ||||
| 
 | ||||
|         // we'll need this when we get to iterating through lists programatically - e.g. ctrl-shift-up/down
 | ||||
| /* | ||||
|         this.listOrder = [ | ||||
|             "im.vector.fake.invite", | ||||
|             "m.favourite", | ||||
|             "im.vector.fake.recent", | ||||
|             "im.vector.fake.direct", | ||||
|             Object.keys(otherTagNames).filter(tagName=>{ | ||||
|                 return (!tagName.match(/^m\.(favourite|lowpriority)$/)); | ||||
|             }).sort(), | ||||
|             "m.lowpriority", | ||||
|             "im.vector.fake.archived" | ||||
|         ]; | ||||
| */ | ||||
| 
 | ||||
|         return lists; | ||||
|         return s; | ||||
|     }, | ||||
| 
 | ||||
|     _getScrollNode: function() { | ||||
|  | @ -455,7 +337,6 @@ module.exports = React.createClass({ | |||
|         var incomingCallBox = document.getElementById("incomingCallBox"); | ||||
|         if (incomingCallBox && incomingCallBox.parentElement) { | ||||
|             var scrollArea = this._getScrollNode(); | ||||
|             if (!scrollArea) return; | ||||
|             // Use the offset of the top of the scroll area from the window
 | ||||
|             // as this is used to calculate the CSS fixed top position for the stickies
 | ||||
|             var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset; | ||||
|  | @ -479,7 +360,6 @@ module.exports = React.createClass({ | |||
|     // properly through React
 | ||||
|     _initAndPositionStickyHeaders: function(initialise, scrollToPosition) { | ||||
|         var scrollArea = this._getScrollNode(); | ||||
|         if (!scrollArea) return; | ||||
|         // Use the offset of the top of the scroll area from the window
 | ||||
|         // as this is used to calculate the CSS fixed top position for the stickies
 | ||||
|         var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset; | ||||
|  | @ -577,74 +457,21 @@ module.exports = React.createClass({ | |||
|         this.refs.gemscroll.forceUpdate(); | ||||
|     }, | ||||
| 
 | ||||
|     _getEmptyContent: function(section) { | ||||
|         const RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget'); | ||||
| 
 | ||||
|         if (this.props.collapsed) { | ||||
|             return <RoomDropTarget label="" />; | ||||
|         } | ||||
| 
 | ||||
|         const StartChatButton = sdk.getComponent('elements.StartChatButton'); | ||||
|         const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton'); | ||||
|         const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton'); | ||||
|         if (this.state.totalRoomCount === 0) { | ||||
|             const TintableSvg = sdk.getComponent('elements.TintableSvg'); | ||||
|             switch (section) { | ||||
|                 case 'im.vector.fake.direct': | ||||
|                     return <div className="mx_RoomList_emptySubListTip"> | ||||
|                         Press | ||||
|                         <StartChatButton size="16" /> | ||||
|                         to start a chat with someone | ||||
|                     </div>; | ||||
|                 case 'im.vector.fake.recent': | ||||
|                     return <div className="mx_RoomList_emptySubListTip"> | ||||
|                         You're not in any rooms yet! Press | ||||
|                         <CreateRoomButton size="16" /> | ||||
|                         to make a room or | ||||
|                         <RoomDirectoryButton size="16" /> | ||||
|                         to browse the directory | ||||
|                     </div>; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const labelText = 'Drop here to ' + (VERBS[section] || 'tag ' + section); | ||||
| 
 | ||||
|         return <RoomDropTarget label={labelText} />; | ||||
|     }, | ||||
| 
 | ||||
|     _getHeaderItems: function(section) { | ||||
|         const StartChatButton = sdk.getComponent('elements.StartChatButton'); | ||||
|         const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton'); | ||||
|         const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton'); | ||||
|         switch (section) { | ||||
|             case 'im.vector.fake.direct': | ||||
|                 return <span className="mx_RoomList_headerButtons"> | ||||
|                     <StartChatButton size="16" /> | ||||
|                 </span>; | ||||
|             case 'im.vector.fake.recent': | ||||
|                 return <span className="mx_RoomList_headerButtons"> | ||||
|                     <RoomDirectoryButton size="16" /> | ||||
|                     <CreateRoomButton size="16" /> | ||||
|                 </span>; | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|         var RoomSubList = sdk.getComponent('structures.RoomSubList'); | ||||
|         var self = this; | ||||
| 
 | ||||
|         return ( | ||||
|             <GeminiScrollbar className="mx_RoomList_scrollbar" | ||||
|                  autoshow={true} onScroll={ self._whenScrolling } onResize={ self._whenScrolling } ref="gemscroll"> | ||||
|             <div className="mx_RoomList" onMouseOver={ this._onMouseOver }> | ||||
|                  autoshow={true} onScroll={ self._whenScrolling } ref="gemscroll"> | ||||
|             <div className="mx_RoomList"> | ||||
|                 <RoomSubList list={ self.state.lists['im.vector.fake.invite'] } | ||||
|                              label="Invites" | ||||
|                              tagName="im.vector.fake.invite" | ||||
|                              editable={ false } | ||||
|                              order="recent" | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              incomingCall={ self.state.incomingCall } | ||||
|                              collapsed={ self.props.collapsed } | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              searchFilter={ self.props.searchFilter } | ||||
|                              onHeaderClick={ self.onSubListHeaderClick } | ||||
|                              onShowMoreRooms={ self.onShowMoreRooms } /> | ||||
|  | @ -652,12 +479,12 @@ module.exports = React.createClass({ | |||
|                 <RoomSubList list={ self.state.lists['m.favourite'] } | ||||
|                              label="Favourites" | ||||
|                              tagName="m.favourite" | ||||
|                              emptyContent={self._getEmptyContent('m.favourite')} | ||||
|                              verb="favourite" | ||||
|                              editable={ true } | ||||
|                              order="manual" | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              incomingCall={ self.state.incomingCall } | ||||
|                              collapsed={ self.props.collapsed } | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              searchFilter={ self.props.searchFilter } | ||||
|                              onHeaderClick={ self.onSubListHeaderClick } | ||||
|                              onShowMoreRooms={ self.onShowMoreRooms } /> | ||||
|  | @ -665,13 +492,12 @@ module.exports = React.createClass({ | |||
|                 <RoomSubList list={ self.state.lists['im.vector.fake.direct'] } | ||||
|                              label="People" | ||||
|                              tagName="im.vector.fake.direct" | ||||
|                              emptyContent={self._getEmptyContent('im.vector.fake.direct')} | ||||
|                              headerItems={self._getHeaderItems('im.vector.fake.direct')} | ||||
|                              verb="tag direct chat" | ||||
|                              editable={ true } | ||||
|                              order="recent" | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              incomingCall={ self.state.incomingCall } | ||||
|                              collapsed={ self.props.collapsed } | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              alwaysShowHeader={ true } | ||||
|                              searchFilter={ self.props.searchFilter } | ||||
|                              onHeaderClick={ self.onSubListHeaderClick } | ||||
|  | @ -679,30 +505,28 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|                 <RoomSubList list={ self.state.lists['im.vector.fake.recent'] } | ||||
|                              label="Rooms" | ||||
|                              tagName="im.vector.fake.recent" | ||||
|                              editable={ true } | ||||
|                              emptyContent={self._getEmptyContent('im.vector.fake.recent')} | ||||
|                              headerItems={self._getHeaderItems('im.vector.fake.recent')} | ||||
|                              verb="restore" | ||||
|                              order="recent" | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              incomingCall={ self.state.incomingCall } | ||||
|                              collapsed={ self.props.collapsed } | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              searchFilter={ self.props.searchFilter } | ||||
|                              onHeaderClick={ self.onSubListHeaderClick } | ||||
|                              onShowMoreRooms={ self.onShowMoreRooms } /> | ||||
| 
 | ||||
|                 { Object.keys(self.state.lists).sort().map(function(tagName) { | ||||
|                 { Object.keys(self.state.lists).map(function(tagName) { | ||||
|                     if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) { | ||||
|                         return <RoomSubList list={ self.state.lists[tagName] } | ||||
|                              key={ tagName } | ||||
|                              label={ tagName } | ||||
|                              tagName={ tagName } | ||||
|                              emptyContent={self._getEmptyContent(tagName)} | ||||
|                              verb={ "tag as " + tagName } | ||||
|                              editable={ true } | ||||
|                              order="manual" | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              incomingCall={ self.state.incomingCall } | ||||
|                              collapsed={ self.props.collapsed } | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              searchFilter={ self.props.searchFilter } | ||||
|                              onHeaderClick={ self.onSubListHeaderClick } | ||||
|                              onShowMoreRooms={ self.onShowMoreRooms } />; | ||||
|  | @ -713,23 +537,22 @@ module.exports = React.createClass({ | |||
|                 <RoomSubList list={ self.state.lists['m.lowpriority'] } | ||||
|                              label="Low priority" | ||||
|                              tagName="m.lowpriority" | ||||
|                              emptyContent={self._getEmptyContent('m.lowpriority')} | ||||
|                              verb="demote" | ||||
|                              editable={ true } | ||||
|                              order="recent" | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              incomingCall={ self.state.incomingCall } | ||||
|                              collapsed={ self.props.collapsed } | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              searchFilter={ self.props.searchFilter } | ||||
|                              onHeaderClick={ self.onSubListHeaderClick } | ||||
|                              onShowMoreRooms={ self.onShowMoreRooms } /> | ||||
| 
 | ||||
|                 <RoomSubList list={ self.state.lists['im.vector.fake.archived'] } | ||||
|                              label="Historical" | ||||
|                              tagName="im.vector.fake.archived" | ||||
|                              editable={ false } | ||||
|                              order="recent" | ||||
|                              collapsed={ self.props.collapsed } | ||||
|                              selectedRoom={ self.props.selectedRoom } | ||||
|                              collapsed={ self.props.collapsed } | ||||
|                              alwaysShowHeader={ true } | ||||
|                              startAsHidden={ true } | ||||
|                              showSpinner={ self.state.isLoadingLeftRooms } | ||||
|  |  | |||
|  | @ -27,8 +27,6 @@ var RoomNotifs = require('../../../RoomNotifs'); | |||
| var FormattingUtils = require('../../../utils/FormattingUtils'); | ||||
| import AccessibleButton from '../elements/AccessibleButton'; | ||||
| var UserSettingsStore = require('../../../UserSettingsStore'); | ||||
| var constantTimeDispatcher = require('../../../ConstantTimeDispatcher'); | ||||
| var Unread = require('../../../Unread'); | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'RoomTile', | ||||
|  | @ -38,10 +36,12 @@ module.exports = React.createClass({ | |||
|         connectDropTarget: React.PropTypes.func, | ||||
|         onClick: React.PropTypes.func, | ||||
|         isDragging: React.PropTypes.bool, | ||||
|         selectedRoom: React.PropTypes.string, | ||||
| 
 | ||||
|         room: React.PropTypes.object.isRequired, | ||||
|         collapsed: React.PropTypes.bool.isRequired, | ||||
|         selected: React.PropTypes.bool.isRequired, | ||||
|         unread: React.PropTypes.bool.isRequired, | ||||
|         highlight: React.PropTypes.bool.isRequired, | ||||
|         isInvite: React.PropTypes.bool.isRequired, | ||||
|         incomingCall: React.PropTypes.object, | ||||
|     }, | ||||
|  | @ -54,11 +54,10 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|     getInitialState: function() { | ||||
|         return({ | ||||
|             hover: false, | ||||
|             badgeHover: false, | ||||
|             hover : false, | ||||
|             badgeHover : false, | ||||
|             menuDisplayed: false, | ||||
|             notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), | ||||
|             selected: this.props.room ? (this.props.selectedRoom === this.props.room.roomId) : false, | ||||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|  | @ -80,32 +79,23 @@ module.exports = React.createClass({ | |||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     onAccountData: function(accountDataEvent) { | ||||
|         if (accountDataEvent.getType() == 'm.push_rules') { | ||||
|             this.setState({ | ||||
|                 notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     componentWillMount: function() { | ||||
|         constantTimeDispatcher.register("RoomTile.refresh", this.props.room.roomId, this.onRefresh); | ||||
|         constantTimeDispatcher.register("RoomTile.select", this.props.room.roomId, this.onSelect); | ||||
|         this.onRefresh(); | ||||
|         MatrixClientPeg.get().on("accountData", this.onAccountData); | ||||
|     }, | ||||
| 
 | ||||
|     componentWillUnmount: function() { | ||||
|         constantTimeDispatcher.unregister("RoomTile.refresh", this.props.room.roomId, this.onRefresh); | ||||
|         constantTimeDispatcher.unregister("RoomTile.select", this.props.room.roomId, this.onSelect); | ||||
|     }, | ||||
| 
 | ||||
|     componentWillReceiveProps: function(nextProps) { | ||||
|         this.onRefresh(); | ||||
|     }, | ||||
| 
 | ||||
|     onRefresh: function(params) { | ||||
|         this.setState({ | ||||
|             unread: Unread.doesRoomHaveUnreadMessages(this.props.room), | ||||
|             highlight: this.props.room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite, | ||||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|     onSelect: function(params) { | ||||
|         this.setState({ | ||||
|             selected: params.selected, | ||||
|         }); | ||||
|         var cli = MatrixClientPeg.get(); | ||||
|         if (cli) { | ||||
|             MatrixClientPeg.get().removeListener("accountData", this.onAccountData); | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     onClick: function(ev) { | ||||
|  | @ -179,13 +169,13 @@ module.exports = React.createClass({ | |||
|         // var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
 | ||||
| 
 | ||||
|         const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge(); | ||||
|         const mentionBadges = this.state.highlight && this._shouldShowMentionBadge(); | ||||
|         const mentionBadges = this.props.highlight && this._shouldShowMentionBadge(); | ||||
|         const badges = notifBadges || mentionBadges; | ||||
| 
 | ||||
|         var classes = classNames({ | ||||
|             'mx_RoomTile': true, | ||||
|             'mx_RoomTile_selected': this.state.selected, | ||||
|             'mx_RoomTile_unread': this.state.unread, | ||||
|             'mx_RoomTile_selected': this.props.selected, | ||||
|             'mx_RoomTile_unread': this.props.unread, | ||||
|             'mx_RoomTile_unreadNotify': notifBadges, | ||||
|             'mx_RoomTile_highlight': mentionBadges, | ||||
|             'mx_RoomTile_invited': (me && me.membership == 'invite'), | ||||
|  | @ -231,7 +221,7 @@ module.exports = React.createClass({ | |||
|                 'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed, | ||||
|             }); | ||||
| 
 | ||||
|             if (this.state.selected) { | ||||
|             if (this.props.selected) { | ||||
|                 let nameSelected = <EmojiText>{name}</EmojiText>; | ||||
| 
 | ||||
|                 label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>; | ||||
|  | @ -265,8 +255,7 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|         let ret = ( | ||||
|             <div> { /* Only native elements can be wrapped in a DnD object. */} | ||||
|             <AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} | ||||
|                               onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> | ||||
|             <AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> | ||||
|                 <div className={avatarClasses}> | ||||
|                     <div className="mx_RoomTile_avatar_container"> | ||||
|                         <RoomAvatar room={this.props.room} width={24} height={24} /> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 David Baker
						David Baker