Merge pull request #2241 from vector-im/dbkr/paginate_publicrooms
Paginate Room Directorypull/2280/head
commit
4720da3f8e
|
@ -29,6 +29,7 @@ var linkify = require('linkifyjs');
|
|||
var linkifyString = require('linkifyjs/string');
|
||||
var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
|
||||
var sanitizeHtml = require('sanitize-html');
|
||||
var q = require('q');
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
|
@ -50,7 +51,6 @@ module.exports = React.createClass({
|
|||
getInitialState: function() {
|
||||
return {
|
||||
publicRooms: [],
|
||||
roomAlias: '',
|
||||
loading: true,
|
||||
filterByNetwork: null,
|
||||
}
|
||||
|
@ -64,6 +64,10 @@ module.exports = React.createClass({
|
|||
this.networkPatterns[network] = new RegExp(this.props.config.networkPatterns[network]);
|
||||
}
|
||||
}
|
||||
this.nextBatch = null;
|
||||
this.filterString = null;
|
||||
this.filterTimeout = null;
|
||||
this.scrollPanel = null;
|
||||
|
||||
// dis.dispatch({
|
||||
// action: 'ui_opacity',
|
||||
|
@ -73,7 +77,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.getPublicRooms();
|
||||
this.refreshRoomList();
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
@ -84,24 +88,48 @@ module.exports = React.createClass({
|
|||
// });
|
||||
},
|
||||
|
||||
getPublicRooms: function() {
|
||||
var self = this;
|
||||
MatrixClientPeg.get().publicRooms(function (err, data) {
|
||||
if (err) {
|
||||
self.setState({ loading: false });
|
||||
refreshRoomList: function() {
|
||||
this.nextBatch = null;
|
||||
this.setState({
|
||||
publicRooms: [],
|
||||
loading: true,
|
||||
});
|
||||
this.getMoreRooms().done();
|
||||
},
|
||||
|
||||
getMoreRooms: function() {
|
||||
const my_filter_string = this.filterString;
|
||||
const opts = {limit: 20};
|
||||
if (this.nextBatch) opts.since = this.nextBatch;
|
||||
if (this.filterString) opts.filter = { generic_search_term: my_filter_string } ;
|
||||
return MatrixClientPeg.get().publicRooms(opts).then((data) => {
|
||||
if (my_filter_string != this.filterString) {
|
||||
// if the filter has changed since this request was sent,
|
||||
// throw away the result (don't even clear the busy flag
|
||||
// since we must still have a request in flight)
|
||||
return;
|
||||
}
|
||||
|
||||
this.nextBatch = data.next_batch;
|
||||
this.setState((s) => {
|
||||
s.publicRooms.push(...data.chunk);
|
||||
s.loading = false;
|
||||
return s;
|
||||
});
|
||||
return Boolean(data.next_batch);
|
||||
}, (err) => {
|
||||
if (my_filter_string != this.filterString) {
|
||||
// as above: we don't care about errors for old
|
||||
// requests either
|
||||
return;
|
||||
}
|
||||
this.setState({ loading: false });
|
||||
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to get public room list",
|
||||
description: err.message
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.setState({
|
||||
publicRooms: data.chunk,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -142,10 +170,10 @@ module.exports = React.createClass({
|
|||
return MatrixClientPeg.get().deleteAlias(alias);
|
||||
}).done(() => {
|
||||
modal.close();
|
||||
this.getPublicRooms();
|
||||
this.refreshRoomList();
|
||||
}, function(err) {
|
||||
modal.close();
|
||||
this.getPublicRooms();
|
||||
this.refreshRoomList();
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to "+step,
|
||||
description: err.toString()
|
||||
|
@ -167,9 +195,47 @@ module.exports = React.createClass({
|
|||
onNetworkChange: function(network) {
|
||||
this.setState({
|
||||
filterByNetwork: network,
|
||||
}, () => {
|
||||
// we just filtered out a bunch of rooms, so check to see if
|
||||
// we need to fill up the scrollpanel again
|
||||
// NB. Because we filter the results, the HS can keep giving
|
||||
// us more rooms and we'll keep requesting more if none match
|
||||
// the filter, which is pretty terrible. We need a way
|
||||
// to filter by network on the server.
|
||||
if (this.scrollPanel) this.scrollPanel.checkFillState();
|
||||
});
|
||||
},
|
||||
|
||||
onFillRequest: function(backwards) {
|
||||
if (backwards || !this.nextBatch) return q(false);
|
||||
|
||||
return this.getMoreRooms();
|
||||
},
|
||||
|
||||
onFilterChange: function(ev) {
|
||||
const alias = ev.target.value;
|
||||
|
||||
this.filterString = alias || null;
|
||||
|
||||
// don't send the request for a little bit,
|
||||
// no point hammering the server with a
|
||||
// request for every keystroke, let the
|
||||
// user finish typing.
|
||||
if (this.filterTimeout) {
|
||||
clearTimeout(this.filterTimeout);
|
||||
}
|
||||
this.filterTimeout = setTimeout(() => {
|
||||
this.filterTimeout = null;
|
||||
this.refreshRoomList();
|
||||
}, 300);
|
||||
},
|
||||
|
||||
onFilterKeyUp: function(ev) {
|
||||
if (ev.key == "Enter") {
|
||||
this.showRoomAlias(ev.target.value);
|
||||
}
|
||||
},
|
||||
|
||||
showRoomAlias: function(alias) {
|
||||
this.showRoom(null, alias);
|
||||
},
|
||||
|
@ -214,23 +280,17 @@ module.exports = React.createClass({
|
|||
dis.dispatch(payload);
|
||||
},
|
||||
|
||||
getRows: function(filter) {
|
||||
getRows: function() {
|
||||
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
|
||||
if (!this.state.publicRooms) return [];
|
||||
|
||||
var rooms = this.state.publicRooms.filter((a) => {
|
||||
// FIXME: if incrementally typing, keep narrowing down the search set
|
||||
// incrementally rather than starting over each time.
|
||||
if (this.state.filterByNetwork) {
|
||||
if (!this._isRoomInNetwork(a, this.state.filterByNetwork)) return false;
|
||||
}
|
||||
|
||||
return (((a.name && a.name.toLowerCase().search(filter.toLowerCase()) >= 0) ||
|
||||
(a.aliases && a.aliases[0].toLowerCase().search(filter.toLowerCase()) >= 0)) &&
|
||||
a.num_joined_members > 0);
|
||||
}).sort(function(a,b) {
|
||||
return a.num_joined_members - b.num_joined_members;
|
||||
return true;
|
||||
});
|
||||
var rows = [];
|
||||
var self = this;
|
||||
|
@ -259,7 +319,7 @@ module.exports = React.createClass({
|
|||
var topic = rooms[i].topic || '';
|
||||
topic = linkifyString(sanitizeHtml(topic));
|
||||
|
||||
rows.unshift(
|
||||
rows.push(
|
||||
<tr key={ rooms[i].room_id }
|
||||
onClick={self.onRoomClicked.bind(self, rooms[i])}
|
||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||
|
@ -289,12 +349,8 @@ module.exports = React.createClass({
|
|||
return rows;
|
||||
},
|
||||
|
||||
onKeyUp: function(ev) {
|
||||
this.forceUpdate();
|
||||
this.setState({ roomAlias : this.refs.roomAlias.value })
|
||||
if (ev.key == "Enter") {
|
||||
this.showRoomAlias(this.refs.roomAlias.value);
|
||||
}
|
||||
collectScrollPanel: function(element) {
|
||||
this.scrollPanel = element;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -312,13 +368,27 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
let content;
|
||||
if (this.state.loading) {
|
||||
var Loader = sdk.getComponent("elements.Spinner");
|
||||
return (
|
||||
<div className="mx_RoomDirectory">
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
content = <div className="mx_RoomDirectory">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
</div>;
|
||||
} else {
|
||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||
content = <ScrollPanel ref={this.collectScrollPanel}
|
||||
className="mx_RoomDirectory_tableWrapper"
|
||||
onFillRequest={ this.onFillRequest }
|
||||
stickyBottom={false}
|
||||
startAtBottom={false}
|
||||
onResize={function(){}}
|
||||
>
|
||||
<table ref="directory_table" className="mx_RoomDirectory_table">
|
||||
<tbody>
|
||||
{ this.getRows() }
|
||||
</tbody>
|
||||
</table>
|
||||
</ScrollPanel>;
|
||||
}
|
||||
|
||||
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
||||
|
@ -328,16 +398,12 @@ module.exports = React.createClass({
|
|||
<SimpleRoomHeader title="Directory" />
|
||||
<div className="mx_RoomDirectory_list">
|
||||
<div className="mx_RoomDirectory_listheader">
|
||||
<input ref="roomAlias" placeholder="Join a room (e.g. #foo:domain.com)" className="mx_RoomDirectory_input" size="64" onKeyUp={ this.onKeyUp }/>
|
||||
<input type="text" placeholder="Find a room by keyword or room ID (#foo:matrix.org)"
|
||||
className="mx_RoomDirectory_input" size="64" onChange={this.onFilterChange} onKeyUp={this.onFilterKeyUp}
|
||||
/>
|
||||
<NetworkDropdown config={this.props.config} onNetworkChange={this.onNetworkChange} />
|
||||
</div>
|
||||
<GeminiScrollbar className="mx_RoomDirectory_tableWrapper">
|
||||
<table ref="directory_table" className="mx_RoomDirectory_table">
|
||||
<tbody>
|
||||
{ this.getRows(this.state.roomAlias) }
|
||||
</tbody>
|
||||
</table>
|
||||
</GeminiScrollbar>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -46,6 +46,11 @@ limitations under the License.
|
|||
-webkit-flex-direction: column;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_list .mx_RoomView_messageListWrapper {
|
||||
justify-content: flex-start;
|
||||
-webkit-justify-content: flex-start;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_listheader {
|
||||
display: table;
|
||||
width: 100%;
|
||||
|
|
|
@ -76,7 +76,7 @@ describe('joining a room', function () {
|
|||
httpBackend.when('GET', '/pushrules').respond(200, {});
|
||||
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
|
||||
httpBackend.when('GET', '/sync').respond(200, {});
|
||||
httpBackend.when('GET', '/publicRooms').respond(200, {chunk: []});
|
||||
httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []});
|
||||
httpBackend.when('GET', '/directory/room/'+encodeURIComponent(ROOM_ALIAS)).respond(200, { room_id: ROOM_ID });
|
||||
|
||||
// start with a logged-in client
|
||||
|
|
Loading…
Reference in New Issue