mirror of https://github.com/vector-im/riot-web
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 linkifyString = require('linkifyjs/string');
|
||||||
var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
|
var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
|
||||||
var sanitizeHtml = require('sanitize-html');
|
var sanitizeHtml = require('sanitize-html');
|
||||||
|
var q = require('q');
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
linkifyMatrix(linkify);
|
||||||
|
|
||||||
|
@ -50,7 +51,6 @@ module.exports = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
publicRooms: [],
|
publicRooms: [],
|
||||||
roomAlias: '',
|
|
||||||
loading: true,
|
loading: true,
|
||||||
filterByNetwork: null,
|
filterByNetwork: null,
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,10 @@ module.exports = React.createClass({
|
||||||
this.networkPatterns[network] = new RegExp(this.props.config.networkPatterns[network]);
|
this.networkPatterns[network] = new RegExp(this.props.config.networkPatterns[network]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.nextBatch = null;
|
||||||
|
this.filterString = null;
|
||||||
|
this.filterTimeout = null;
|
||||||
|
this.scrollPanel = null;
|
||||||
|
|
||||||
// dis.dispatch({
|
// dis.dispatch({
|
||||||
// action: 'ui_opacity',
|
// action: 'ui_opacity',
|
||||||
|
@ -73,7 +77,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this.getPublicRooms();
|
this.refreshRoomList();
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
|
@ -84,24 +88,48 @@ module.exports = React.createClass({
|
||||||
// });
|
// });
|
||||||
},
|
},
|
||||||
|
|
||||||
getPublicRooms: function() {
|
refreshRoomList: function() {
|
||||||
var self = this;
|
this.nextBatch = null;
|
||||||
MatrixClientPeg.get().publicRooms(function (err, data) {
|
this.setState({
|
||||||
if (err) {
|
publicRooms: [],
|
||||||
self.setState({ loading: false });
|
loading: true,
|
||||||
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
|
});
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
this.getMoreRooms().done();
|
||||||
Modal.createDialog(ErrorDialog, {
|
},
|
||||||
title: "Failed to get public room list",
|
|
||||||
description: err.message
|
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;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
self.setState({
|
this.nextBatch = data.next_batch;
|
||||||
publicRooms: data.chunk,
|
this.setState((s) => {
|
||||||
loading: false,
|
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
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -142,10 +170,10 @@ module.exports = React.createClass({
|
||||||
return MatrixClientPeg.get().deleteAlias(alias);
|
return MatrixClientPeg.get().deleteAlias(alias);
|
||||||
}).done(() => {
|
}).done(() => {
|
||||||
modal.close();
|
modal.close();
|
||||||
this.getPublicRooms();
|
this.refreshRoomList();
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
modal.close();
|
modal.close();
|
||||||
this.getPublicRooms();
|
this.refreshRoomList();
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Failed to "+step,
|
title: "Failed to "+step,
|
||||||
description: err.toString()
|
description: err.toString()
|
||||||
|
@ -167,9 +195,47 @@ module.exports = React.createClass({
|
||||||
onNetworkChange: function(network) {
|
onNetworkChange: function(network) {
|
||||||
this.setState({
|
this.setState({
|
||||||
filterByNetwork: network,
|
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) {
|
showRoomAlias: function(alias) {
|
||||||
this.showRoom(null, alias);
|
this.showRoom(null, alias);
|
||||||
},
|
},
|
||||||
|
@ -214,23 +280,17 @@ module.exports = React.createClass({
|
||||||
dis.dispatch(payload);
|
dis.dispatch(payload);
|
||||||
},
|
},
|
||||||
|
|
||||||
getRows: function(filter) {
|
getRows: function() {
|
||||||
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
|
|
||||||
if (!this.state.publicRooms) return [];
|
if (!this.state.publicRooms) return [];
|
||||||
|
|
||||||
var rooms = this.state.publicRooms.filter((a) => {
|
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.state.filterByNetwork) {
|
||||||
if (!this._isRoomInNetwork(a, this.state.filterByNetwork)) return false;
|
if (!this._isRoomInNetwork(a, this.state.filterByNetwork)) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (((a.name && a.name.toLowerCase().search(filter.toLowerCase()) >= 0) ||
|
return true;
|
||||||
(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;
|
|
||||||
});
|
});
|
||||||
var rows = [];
|
var rows = [];
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -259,7 +319,7 @@ module.exports = React.createClass({
|
||||||
var topic = rooms[i].topic || '';
|
var topic = rooms[i].topic || '';
|
||||||
topic = linkifyString(sanitizeHtml(topic));
|
topic = linkifyString(sanitizeHtml(topic));
|
||||||
|
|
||||||
rows.unshift(
|
rows.push(
|
||||||
<tr key={ rooms[i].room_id }
|
<tr key={ rooms[i].room_id }
|
||||||
onClick={self.onRoomClicked.bind(self, rooms[i])}
|
onClick={self.onRoomClicked.bind(self, rooms[i])}
|
||||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
|
@ -289,12 +349,8 @@ module.exports = React.createClass({
|
||||||
return rows;
|
return rows;
|
||||||
},
|
},
|
||||||
|
|
||||||
onKeyUp: function(ev) {
|
collectScrollPanel: function(element) {
|
||||||
this.forceUpdate();
|
this.scrollPanel = element;
|
||||||
this.setState({ roomAlias : this.refs.roomAlias.value })
|
|
||||||
if (ev.key == "Enter") {
|
|
||||||
this.showRoomAlias(this.refs.roomAlias.value);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -312,13 +368,27 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
let content;
|
||||||
if (this.state.loading) {
|
if (this.state.loading) {
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
return (
|
content = <div className="mx_RoomDirectory">
|
||||||
<div className="mx_RoomDirectory">
|
<Loader />
|
||||||
<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');
|
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
||||||
|
@ -328,16 +398,12 @@ module.exports = React.createClass({
|
||||||
<SimpleRoomHeader title="Directory" />
|
<SimpleRoomHeader title="Directory" />
|
||||||
<div className="mx_RoomDirectory_list">
|
<div className="mx_RoomDirectory_list">
|
||||||
<div className="mx_RoomDirectory_listheader">
|
<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} />
|
<NetworkDropdown config={this.props.config} onNetworkChange={this.onNetworkChange} />
|
||||||
</div>
|
</div>
|
||||||
<GeminiScrollbar className="mx_RoomDirectory_tableWrapper">
|
{content}
|
||||||
<table ref="directory_table" className="mx_RoomDirectory_table">
|
|
||||||
<tbody>
|
|
||||||
{ this.getRows(this.state.roomAlias) }
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</GeminiScrollbar>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -46,6 +46,11 @@ limitations under the License.
|
||||||
-webkit-flex-direction: column;
|
-webkit-flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomDirectory_list .mx_RoomView_messageListWrapper {
|
||||||
|
justify-content: flex-start;
|
||||||
|
-webkit-justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_listheader {
|
.mx_RoomDirectory_listheader {
|
||||||
display: table;
|
display: table;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -76,7 +76,7 @@ describe('joining a room', function () {
|
||||||
httpBackend.when('GET', '/pushrules').respond(200, {});
|
httpBackend.when('GET', '/pushrules').respond(200, {});
|
||||||
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
|
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
|
||||||
httpBackend.when('GET', '/sync').respond(200, {});
|
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 });
|
httpBackend.when('GET', '/directory/room/'+encodeURIComponent(ROOM_ALIAS)).respond(200, { room_id: ROOM_ID });
|
||||||
|
|
||||||
// start with a logged-in client
|
// start with a logged-in client
|
||||||
|
|
Loading…
Reference in New Issue