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.
This commit is contained in:
commit
8ce6da1b16
15 changed files with 127 additions and 689 deletions
|
@ -65,12 +65,11 @@
|
||||||
"lodash": "^4.13.1",
|
"lodash": "^4.13.1",
|
||||||
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
|
||||||
"optimist": "^0.6.1",
|
"optimist": "^0.6.1",
|
||||||
"prop-types": "^15.5.8",
|
|
||||||
"q": "^1.4.1",
|
"q": "^1.4.1",
|
||||||
"react": "^15.4.0",
|
"react": "^15.4.0",
|
||||||
"react-addons-css-transition-group": "15.3.2",
|
"react-addons-css-transition-group": "15.3.2",
|
||||||
"react-dom": "^15.4.0",
|
"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",
|
"sanitize-html": "^1.11.1",
|
||||||
"text-encoding-utf-8": "^1.0.1",
|
"text-encoding-utf-8": "^1.0.1",
|
||||||
"velocity-vector": "vector-im/velocity#059e3b2",
|
"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,
|
DELETE: 46,
|
||||||
KEY_D: 68,
|
KEY_D: 68,
|
||||||
KEY_E: 69,
|
KEY_E: 69,
|
||||||
KEY_K: 75,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -107,18 +107,6 @@ export default React.createClass({
|
||||||
var handled = false;
|
var handled = false;
|
||||||
|
|
||||||
switch (ev.keyCode) {
|
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.UP:
|
||||||
case KeyCode.DOWN:
|
case KeyCode.DOWN:
|
||||||
if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
|
if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
|
||||||
|
|
|
@ -590,7 +590,6 @@ var TimelinePanel = React.createClass({
|
||||||
this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
|
this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'on_room_read',
|
action: 'on_room_read',
|
||||||
room: this.props.timelineSet.room,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,35 +47,18 @@ export default React.createClass({
|
||||||
children: React.PropTypes.node,
|
children: React.PropTypes.node,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
_onKeyDown: function(e) {
|
||||||
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) {
|
|
||||||
if (e.keyCode === KeyCode.ESCAPE) {
|
if (e.keyCode === KeyCode.ESCAPE) {
|
||||||
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
} else if (e.keyCode === KeyCode.ENTER) {
|
} else if (e.keyCode === KeyCode.ENTER) {
|
||||||
if (this.props.onEnterPressed) {
|
if (this.props.onEnterPressed) {
|
||||||
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.onEnterPressed(e);
|
this.props.onEnterPressed(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Consume all keyup events while Modal is open
|
|
||||||
e.stopPropagation();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onCancelClick: function(e) {
|
_onCancelClick: function(e) {
|
||||||
|
@ -84,13 +67,9 @@ export default React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onKeyUp={this._onKeyUp}
|
<div onKeyDown={this._onKeyDown} className={this.props.className}>
|
||||||
onKeyDown={this._eatKeyEvent}
|
|
||||||
onKeyPress={this._eatKeyEvent}
|
|
||||||
className={this.props.className}
|
|
||||||
>
|
|
||||||
<AccessibleButton onClick={this._onCancelClick}
|
<AccessibleButton onClick={this._onCancelClick}
|
||||||
className="mx_Dialog_cancelButton"
|
className="mx_Dialog_cancelButton"
|
||||||
>
|
>
|
||||||
|
|
|
@ -47,12 +47,6 @@ export default React.createClass({
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
if (this.props.focus) {
|
|
||||||
this.refs.button.focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const cancelButton = this.props.hasCancelButton ? (
|
const cancelButton = this.props.hasCancelButton ? (
|
||||||
|
@ -69,7 +63,7 @@ export default React.createClass({
|
||||||
{this.props.description}
|
{this.props.description}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<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}
|
{this.props.button}
|
||||||
</button>
|
</button>
|
||||||
{this.props.extraButtons}
|
{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 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2017 Vector Creations Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -22,23 +21,15 @@ var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var CallHandler = require('../../../CallHandler');
|
var CallHandler = require('../../../CallHandler');
|
||||||
var RoomListSorter = require("../../../RoomListSorter");
|
var RoomListSorter = require("../../../RoomListSorter");
|
||||||
|
var Unread = require('../../../Unread');
|
||||||
var dis = require("../../../dispatcher");
|
var dis = require("../../../dispatcher");
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var rate_limited_func = require('../../../ratelimitedfunc');
|
var rate_limited_func = require('../../../ratelimitedfunc');
|
||||||
var Rooms = require('../../../Rooms');
|
var Rooms = require('../../../Rooms');
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
var Receipt = require('../../../utils/Receipt');
|
var Receipt = require('../../../utils/Receipt');
|
||||||
var constantTimeDispatcher = require('../../../ConstantTimeDispatcher');
|
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
|
||||||
|
|
||||||
const HIDE_CONFERENCE_CHANS = true;
|
var HIDE_CONFERENCE_CHANS = true;
|
||||||
|
|
||||||
const VERBS = {
|
|
||||||
'm.favourite': 'favourite',
|
|
||||||
'im.vector.fake.direct': 'tag direct chat',
|
|
||||||
'im.vector.fake.recent': 'restore',
|
|
||||||
'm.lowpriority': 'demote',
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomList',
|
displayName: 'RoomList',
|
||||||
|
@ -46,23 +37,13 @@ module.exports = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
ConferenceHandler: React.PropTypes.any,
|
ConferenceHandler: React.PropTypes.any,
|
||||||
collapsed: React.PropTypes.bool.isRequired,
|
collapsed: React.PropTypes.bool.isRequired,
|
||||||
selectedRoom: React.PropTypes.string,
|
currentRoom: React.PropTypes.string,
|
||||||
searchFilter: 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() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
isLoadingLeftRooms: false,
|
isLoadingLeftRooms: false,
|
||||||
totalRoomCount: null,
|
|
||||||
lists: {},
|
lists: {},
|
||||||
incomingCall: null,
|
incomingCall: null,
|
||||||
};
|
};
|
||||||
|
@ -76,21 +57,12 @@ module.exports = React.createClass({
|
||||||
cli.on("Room.name", this.onRoomName);
|
cli.on("Room.name", this.onRoomName);
|
||||||
cli.on("Room.tags", this.onRoomTags);
|
cli.on("Room.tags", this.onRoomTags);
|
||||||
cli.on("Room.receipt", this.onRoomReceipt);
|
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("RoomMember.name", this.onRoomMemberName);
|
||||||
cli.on("accountData", this.onAccountData);
|
cli.on("accountData", this.onAccountData);
|
||||||
|
|
||||||
// lookup for which lists a given roomId is currently in.
|
var s = this.getRoomLists();
|
||||||
this.listsForRoomId = {};
|
this.setState(s);
|
||||||
|
|
||||||
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
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
@ -99,22 +71,7 @@ module.exports = React.createClass({
|
||||||
this._updateStickyHeaders(true);
|
this._updateStickyHeaders(true);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function(nextProps) {
|
componentDidUpdate: function() {
|
||||||
// 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) {
|
|
||||||
// Reinitialise the stickyHeaders when the component is updated
|
// Reinitialise the stickyHeaders when the component is updated
|
||||||
this._updateStickyHeaders(true);
|
this._updateStickyHeaders(true);
|
||||||
this._repositionIncomingCallBox(undefined, false);
|
this._repositionIncomingCallBox(undefined, false);
|
||||||
|
@ -140,24 +97,10 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'on_room_read':
|
case 'on_room_read':
|
||||||
// poke the right RoomTile to refresh, using the constantTimeDispatcher
|
// Force an update because the notif count state is too deep to cause
|
||||||
// to avoid each and every RoomTile registering to the 'on_room_read' event
|
// an update. This forces the local echo of reading notifs to be
|
||||||
// XXX: if we like the constantTimeDispatcher we might want to dispatch
|
// reflected by the RoomTiles.
|
||||||
// directly from TimelinePanel rather than needlessly bouncing via here.
|
this.forceUpdate();
|
||||||
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 }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -171,7 +114,7 @@ module.exports = React.createClass({
|
||||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||||
MatrixClientPeg.get().removeListener("Room.tags", this.onRoomTags);
|
MatrixClientPeg.get().removeListener("Room.tags", this.onRoomTags);
|
||||||
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
|
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("RoomMember.name", this.onRoomMemberName);
|
||||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||||
}
|
}
|
||||||
|
@ -180,14 +123,10 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoom: function(room) {
|
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();
|
this._delayedRefreshRoomList();
|
||||||
},
|
},
|
||||||
|
|
||||||
onDeleteRoom: function(roomId) {
|
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();
|
this._delayedRefreshRoomList();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -210,10 +149,6 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onMouseOver: function(ev) {
|
|
||||||
this._lastMouseOverTs = Date.now();
|
|
||||||
},
|
|
||||||
|
|
||||||
onSubListHeaderClick: function(isHidden, scrollToPosition) {
|
onSubListHeaderClick: function(isHidden, scrollToPosition) {
|
||||||
// The scroll area has expanded or contracted, so re-calculate sticky headers positions
|
// The scroll area has expanded or contracted, so re-calculate sticky headers positions
|
||||||
this._updateStickyHeaders(true, scrollToPosition);
|
this._updateStickyHeaders(true, scrollToPosition);
|
||||||
|
@ -223,98 +158,41 @@ module.exports = React.createClass({
|
||||||
if (toStartOfTimeline) return;
|
if (toStartOfTimeline) return;
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
||||||
|
this._delayedRefreshRoomList();
|
||||||
// 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, {}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomReceipt: function(receiptEvent, room) {
|
onRoomReceipt: function(receiptEvent, room) {
|
||||||
// because if we read a notification, it will affect notification count
|
// because if we read a notification, it will affect notification count
|
||||||
// only bother updating if there's a receipt from us
|
// only bother updating if there's a receipt from us
|
||||||
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
|
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
|
||||||
var lists = this.listsForRoomId[room.roomId];
|
this._delayedRefreshRoomList();
|
||||||
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, {}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomName: function(room) {
|
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();
|
this._delayedRefreshRoomList();
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomStateMember: function(ev, state, member) {
|
onRoomTags: function(event, room) {
|
||||||
if (ev.getStateKey() === MatrixClientPeg.get().credentials.userId &&
|
this._delayedRefreshRoomList();
|
||||||
ev.getPrevContent() && ev.getPrevContent().membership === "invite")
|
},
|
||||||
{
|
|
||||||
this._delayedRefreshRoomList();
|
onRoomStateEvents: function(ev, state) {
|
||||||
}
|
this._delayedRefreshRoomList();
|
||||||
else {
|
|
||||||
constantTimeDispatcher.dispatch(
|
|
||||||
"RoomTile.refresh", member.roomId, {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomMemberName: function(ev, member) {
|
onRoomMemberName: function(ev, member) {
|
||||||
constantTimeDispatcher.dispatch(
|
this._delayedRefreshRoomList();
|
||||||
"RoomTile.refresh", member.roomId, {}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onAccountData: function(ev) {
|
onAccountData: function(ev) {
|
||||||
if (ev.getType() == 'm.direct') {
|
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();
|
this._delayedRefreshRoomList();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_delayedRefreshRoomList: new rate_limited_func(function() {
|
_delayedRefreshRoomList: new rate_limited_func(function() {
|
||||||
// if the mouse has been moving over the RoomList in the last 500ms
|
this.refreshRoomList();
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}, 500),
|
}, 500),
|
||||||
|
|
||||||
refreshRoomList: function() {
|
refreshRoomList: function() {
|
||||||
|
@ -322,36 +200,27 @@ module.exports = React.createClass({
|
||||||
// (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs))
|
// (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs))
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// TODO: ideally we'd calculate this once at start, and then maintain
|
// TODO: rather than bluntly regenerating and re-sorting everything
|
||||||
// any changes to it incrementally, updating the appropriate sublists
|
// every time we see any kind of room change from the JS SDK
|
||||||
// as needed.
|
// we could do incremental updates on our copy of the state
|
||||||
// Alternatively we'd do something magical with Immutable.js or similar.
|
// based on the room which has actually changed. This would stop
|
||||||
const lists = this.getRoomLists();
|
// us re-rendering all the sublists every time anything changes anywhere
|
||||||
let totalRooms = 0;
|
// in the state of the client.
|
||||||
for (const l of Object.values(lists)) {
|
this.setState(this.getRoomLists());
|
||||||
totalRooms += l.length;
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
lists: this.getRoomLists(),
|
|
||||||
totalRoomCount: totalRooms,
|
|
||||||
});
|
|
||||||
|
|
||||||
// this._lastRefreshRoomListTs = Date.now();
|
// this._lastRefreshRoomListTs = Date.now();
|
||||||
},
|
},
|
||||||
|
|
||||||
getRoomLists: function() {
|
getRoomLists: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
const lists = {};
|
var s = { lists: {} };
|
||||||
|
|
||||||
lists["im.vector.fake.invite"] = [];
|
s.lists["im.vector.fake.invite"] = [];
|
||||||
lists["m.favourite"] = [];
|
s.lists["m.favourite"] = [];
|
||||||
lists["im.vector.fake.recent"] = [];
|
s.lists["im.vector.fake.recent"] = [];
|
||||||
lists["im.vector.fake.direct"] = [];
|
s.lists["im.vector.fake.direct"] = [];
|
||||||
lists["m.lowpriority"] = [];
|
s.lists["m.lowpriority"] = [];
|
||||||
lists["im.vector.fake.archived"] = [];
|
s.lists["im.vector.fake.archived"] = [];
|
||||||
|
|
||||||
this.listsForRoomId = {};
|
|
||||||
var otherTagNames = {};
|
|
||||||
|
|
||||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||||
|
|
||||||
|
@ -364,13 +233,8 @@ module.exports = React.createClass({
|
||||||
// ", target = " + me.events.member.getStateKey() +
|
// ", target = " + me.events.member.getStateKey() +
|
||||||
// ", prevMembership = " + me.events.member.getPrevContent().membership);
|
// ", prevMembership = " + me.events.member.getPrevContent().membership);
|
||||||
|
|
||||||
if (!self.listsForRoomId[room.roomId]) {
|
|
||||||
self.listsForRoomId[room.roomId] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me.membership == "invite") {
|
if (me.membership == "invite") {
|
||||||
self.listsForRoomId[room.roomId].push("im.vector.fake.invite");
|
s.lists["im.vector.fake.invite"].push(room);
|
||||||
lists["im.vector.fake.invite"].push(room);
|
|
||||||
}
|
}
|
||||||
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
|
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
|
||||||
// skip past this room & don't put it in any lists
|
// 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
|
// Used to split rooms via tags
|
||||||
var tagNames = Object.keys(room.tags);
|
var tagNames = Object.keys(room.tags);
|
||||||
|
|
||||||
if (tagNames.length) {
|
if (tagNames.length) {
|
||||||
for (var i = 0; i < tagNames.length; i++) {
|
for (var i = 0; i < tagNames.length; i++) {
|
||||||
var tagName = tagNames[i];
|
var tagName = tagNames[i];
|
||||||
lists[tagName] = lists[tagName] || [];
|
s.lists[tagName] = s.lists[tagName] || [];
|
||||||
lists[tagName].push(room);
|
s.lists[tagNames[i]].push(room);
|
||||||
self.listsForRoomId[room.roomId].push(tagName);
|
|
||||||
otherTagNames[tagName] = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
||||||
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
|
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
|
||||||
self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
|
s.lists["im.vector.fake.direct"].push(room);
|
||||||
lists["im.vector.fake.direct"].push(room);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
|
s.lists["im.vector.fake.recent"].push(room);
|
||||||
lists["im.vector.fake.recent"].push(room);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (me.membership === "leave") {
|
else if (me.membership === "leave") {
|
||||||
self.listsForRoomId[room.roomId].push("im.vector.fake.archived");
|
s.lists["im.vector.fake.archived"].push(room);
|
||||||
lists["im.vector.fake.archived"].push(room);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.error("unrecognised membership: " + me.membership + " - this should never happen");
|
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 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
|
return s;
|
||||||
/*
|
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_getScrollNode: function() {
|
_getScrollNode: function() {
|
||||||
|
@ -455,7 +337,6 @@ module.exports = React.createClass({
|
||||||
var incomingCallBox = document.getElementById("incomingCallBox");
|
var incomingCallBox = document.getElementById("incomingCallBox");
|
||||||
if (incomingCallBox && incomingCallBox.parentElement) {
|
if (incomingCallBox && incomingCallBox.parentElement) {
|
||||||
var scrollArea = this._getScrollNode();
|
var scrollArea = this._getScrollNode();
|
||||||
if (!scrollArea) return;
|
|
||||||
// Use the offset of the top of the scroll area from the window
|
// 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
|
// as this is used to calculate the CSS fixed top position for the stickies
|
||||||
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
|
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
|
||||||
|
@ -479,7 +360,6 @@ module.exports = React.createClass({
|
||||||
// properly through React
|
// properly through React
|
||||||
_initAndPositionStickyHeaders: function(initialise, scrollToPosition) {
|
_initAndPositionStickyHeaders: function(initialise, scrollToPosition) {
|
||||||
var scrollArea = this._getScrollNode();
|
var scrollArea = this._getScrollNode();
|
||||||
if (!scrollArea) return;
|
|
||||||
// Use the offset of the top of the scroll area from the window
|
// 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
|
// as this is used to calculate the CSS fixed top position for the stickies
|
||||||
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
|
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
|
||||||
|
@ -577,74 +457,21 @@ module.exports = React.createClass({
|
||||||
this.refs.gemscroll.forceUpdate();
|
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() {
|
render: function() {
|
||||||
var RoomSubList = sdk.getComponent('structures.RoomSubList');
|
var RoomSubList = sdk.getComponent('structures.RoomSubList');
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeminiScrollbar className="mx_RoomList_scrollbar"
|
<GeminiScrollbar className="mx_RoomList_scrollbar"
|
||||||
autoshow={true} onScroll={ self._whenScrolling } onResize={ self._whenScrolling } ref="gemscroll">
|
autoshow={true} onScroll={ self._whenScrolling } ref="gemscroll">
|
||||||
<div className="mx_RoomList" onMouseOver={ this._onMouseOver }>
|
<div className="mx_RoomList">
|
||||||
<RoomSubList list={ self.state.lists['im.vector.fake.invite'] }
|
<RoomSubList list={ self.state.lists['im.vector.fake.invite'] }
|
||||||
label="Invites"
|
label="Invites"
|
||||||
tagName="im.vector.fake.invite"
|
|
||||||
editable={ false }
|
editable={ false }
|
||||||
order="recent"
|
order="recent"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
@ -652,12 +479,12 @@ module.exports = React.createClass({
|
||||||
<RoomSubList list={ self.state.lists['m.favourite'] }
|
<RoomSubList list={ self.state.lists['m.favourite'] }
|
||||||
label="Favourites"
|
label="Favourites"
|
||||||
tagName="m.favourite"
|
tagName="m.favourite"
|
||||||
emptyContent={self._getEmptyContent('m.favourite')}
|
verb="favourite"
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="manual"
|
order="manual"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
@ -665,13 +492,12 @@ module.exports = React.createClass({
|
||||||
<RoomSubList list={ self.state.lists['im.vector.fake.direct'] }
|
<RoomSubList list={ self.state.lists['im.vector.fake.direct'] }
|
||||||
label="People"
|
label="People"
|
||||||
tagName="im.vector.fake.direct"
|
tagName="im.vector.fake.direct"
|
||||||
emptyContent={self._getEmptyContent('im.vector.fake.direct')}
|
verb="tag direct chat"
|
||||||
headerItems={self._getHeaderItems('im.vector.fake.direct')}
|
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="recent"
|
order="recent"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
alwaysShowHeader={ true }
|
alwaysShowHeader={ true }
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
|
@ -679,30 +505,28 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
<RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
|
<RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
|
||||||
label="Rooms"
|
label="Rooms"
|
||||||
tagName="im.vector.fake.recent"
|
|
||||||
editable={ true }
|
editable={ true }
|
||||||
emptyContent={self._getEmptyContent('im.vector.fake.recent')}
|
verb="restore"
|
||||||
headerItems={self._getHeaderItems('im.vector.fake.recent')}
|
|
||||||
order="recent"
|
order="recent"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
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))$/)) {
|
if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
||||||
return <RoomSubList list={ self.state.lists[tagName] }
|
return <RoomSubList list={ self.state.lists[tagName] }
|
||||||
key={ tagName }
|
key={ tagName }
|
||||||
label={ tagName }
|
label={ tagName }
|
||||||
tagName={ tagName }
|
tagName={ tagName }
|
||||||
emptyContent={self._getEmptyContent(tagName)}
|
verb={ "tag as " + tagName }
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="manual"
|
order="manual"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />;
|
onShowMoreRooms={ self.onShowMoreRooms } />;
|
||||||
|
@ -713,23 +537,22 @@ module.exports = React.createClass({
|
||||||
<RoomSubList list={ self.state.lists['m.lowpriority'] }
|
<RoomSubList list={ self.state.lists['m.lowpriority'] }
|
||||||
label="Low priority"
|
label="Low priority"
|
||||||
tagName="m.lowpriority"
|
tagName="m.lowpriority"
|
||||||
emptyContent={self._getEmptyContent('m.lowpriority')}
|
verb="demote"
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="recent"
|
order="recent"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
|
||||||
<RoomSubList list={ self.state.lists['im.vector.fake.archived'] }
|
<RoomSubList list={ self.state.lists['im.vector.fake.archived'] }
|
||||||
label="Historical"
|
label="Historical"
|
||||||
tagName="im.vector.fake.archived"
|
|
||||||
editable={ false }
|
editable={ false }
|
||||||
order="recent"
|
order="recent"
|
||||||
collapsed={ self.props.collapsed }
|
|
||||||
selectedRoom={ self.props.selectedRoom }
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
collapsed={ self.props.collapsed }
|
||||||
alwaysShowHeader={ true }
|
alwaysShowHeader={ true }
|
||||||
startAsHidden={ true }
|
startAsHidden={ true }
|
||||||
showSpinner={ self.state.isLoadingLeftRooms }
|
showSpinner={ self.state.isLoadingLeftRooms }
|
||||||
|
|
|
@ -27,8 +27,6 @@ var RoomNotifs = require('../../../RoomNotifs');
|
||||||
var FormattingUtils = require('../../../utils/FormattingUtils');
|
var FormattingUtils = require('../../../utils/FormattingUtils');
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
var UserSettingsStore = require('../../../UserSettingsStore');
|
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||||
var constantTimeDispatcher = require('../../../ConstantTimeDispatcher');
|
|
||||||
var Unread = require('../../../Unread');
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomTile',
|
displayName: 'RoomTile',
|
||||||
|
@ -38,10 +36,12 @@ module.exports = React.createClass({
|
||||||
connectDropTarget: React.PropTypes.func,
|
connectDropTarget: React.PropTypes.func,
|
||||||
onClick: React.PropTypes.func,
|
onClick: React.PropTypes.func,
|
||||||
isDragging: React.PropTypes.bool,
|
isDragging: React.PropTypes.bool,
|
||||||
selectedRoom: React.PropTypes.string,
|
|
||||||
|
|
||||||
room: React.PropTypes.object.isRequired,
|
room: React.PropTypes.object.isRequired,
|
||||||
collapsed: React.PropTypes.bool.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,
|
isInvite: React.PropTypes.bool.isRequired,
|
||||||
incomingCall: React.PropTypes.object,
|
incomingCall: React.PropTypes.object,
|
||||||
},
|
},
|
||||||
|
@ -54,11 +54,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return({
|
return({
|
||||||
hover: false,
|
hover : false,
|
||||||
badgeHover: false,
|
badgeHover : false,
|
||||||
menuDisplayed: false,
|
menuDisplayed: false,
|
||||||
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
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() {
|
componentWillMount: function() {
|
||||||
constantTimeDispatcher.register("RoomTile.refresh", this.props.room.roomId, this.onRefresh);
|
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||||
constantTimeDispatcher.register("RoomTile.select", this.props.room.roomId, this.onSelect);
|
|
||||||
this.onRefresh();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
constantTimeDispatcher.unregister("RoomTile.refresh", this.props.room.roomId, this.onRefresh);
|
var cli = MatrixClientPeg.get();
|
||||||
constantTimeDispatcher.unregister("RoomTile.select", this.props.room.roomId, this.onSelect);
|
if (cli) {
|
||||||
},
|
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||||
|
}
|
||||||
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,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick: function(ev) {
|
onClick: function(ev) {
|
||||||
|
@ -179,13 +169,13 @@ module.exports = React.createClass({
|
||||||
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
|
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
|
||||||
|
|
||||||
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();
|
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();
|
||||||
const mentionBadges = this.state.highlight && this._shouldShowMentionBadge();
|
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
|
||||||
const badges = notifBadges || mentionBadges;
|
const badges = notifBadges || mentionBadges;
|
||||||
|
|
||||||
var classes = classNames({
|
var classes = classNames({
|
||||||
'mx_RoomTile': true,
|
'mx_RoomTile': true,
|
||||||
'mx_RoomTile_selected': this.state.selected,
|
'mx_RoomTile_selected': this.props.selected,
|
||||||
'mx_RoomTile_unread': this.state.unread,
|
'mx_RoomTile_unread': this.props.unread,
|
||||||
'mx_RoomTile_unreadNotify': notifBadges,
|
'mx_RoomTile_unreadNotify': notifBadges,
|
||||||
'mx_RoomTile_highlight': mentionBadges,
|
'mx_RoomTile_highlight': mentionBadges,
|
||||||
'mx_RoomTile_invited': (me && me.membership == 'invite'),
|
'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,
|
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.state.selected) {
|
if (this.props.selected) {
|
||||||
let nameSelected = <EmojiText>{name}</EmojiText>;
|
let nameSelected = <EmojiText>{name}</EmojiText>;
|
||||||
|
|
||||||
label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>;
|
label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>;
|
||||||
|
@ -265,8 +255,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let ret = (
|
let ret = (
|
||||||
<div> { /* Only native elements can be wrapped in a DnD object. */}
|
<div> { /* Only native elements can be wrapped in a DnD object. */}
|
||||||
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick}
|
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
|
||||||
<div className={avatarClasses}>
|
<div className={avatarClasses}>
|
||||||
<div className="mx_RoomTile_avatar_container">
|
<div className="mx_RoomTile_avatar_container">
|
||||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue