Refactor the search stuff in RoomView
* factor out the call to MatrixClient.search to a separate _getSearchBatch (so that we can reuse it for paginated results in a bit) * Don't group cross-room searches by room - just display them in timeline order.
This commit is contained in:
parent
9931ef1971
commit
4b271a332e
2 changed files with 109 additions and 102 deletions
|
@ -490,75 +490,65 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onSearch: function(term, scope) {
|
onSearch: function(term, scope) {
|
||||||
var filter;
|
this.setState({
|
||||||
if (scope === "Room") {
|
searchTerm: term,
|
||||||
filter = {
|
searchScope: scope,
|
||||||
// XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :(
|
searchResults: [],
|
||||||
rooms: [
|
searchHighlights: [],
|
||||||
this.props.roomId
|
searchCount: null,
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
self.setState({
|
|
||||||
searchInProgress: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
MatrixClientPeg.get().search({
|
this._getSearchBatch(term, scope);
|
||||||
body: {
|
|
||||||
search_categories: {
|
|
||||||
room_events: {
|
|
||||||
search_term: term,
|
|
||||||
filter: filter,
|
|
||||||
order_by: "recent",
|
|
||||||
include_state: true,
|
|
||||||
groupings: {
|
|
||||||
group_by: [
|
|
||||||
{
|
|
||||||
key: "room_id"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
event_context: {
|
|
||||||
before_limit: 1,
|
|
||||||
after_limit: 1,
|
|
||||||
include_profile: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).then(function(data) {
|
|
||||||
|
|
||||||
if (!self.state.searching || term !== self.refs.search_bar.refs.search_term.value) {
|
// fire off a request for a batch of search results
|
||||||
|
_getSearchBatch: function(term, scope) {
|
||||||
|
this.setState({
|
||||||
|
searchInProgress: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// make sure that we don't end up merging results from
|
||||||
|
// different searches by keeping a unique id.
|
||||||
|
//
|
||||||
|
// todo: should cancel any previous search requests.
|
||||||
|
var searchId = this.searchId = new Date().getTime();
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
MatrixClientPeg.get().search({ body: this._getSearchCondition(term, scope) })
|
||||||
|
.then(function(data) {
|
||||||
|
if (!self.state.searching || self.searchId != searchId) {
|
||||||
console.error("Discarding stale search results");
|
console.error("Discarding stale search results");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// for debugging:
|
var results = data.search_categories.room_events;
|
||||||
// data.search_categories.room_events.highlights = ["hello", "everybody"];
|
|
||||||
|
|
||||||
var highlights;
|
|
||||||
if (data.search_categories.room_events.highlights &&
|
|
||||||
data.search_categories.room_events.highlights.length > 0)
|
|
||||||
{
|
|
||||||
// postgres on synapse returns us precise details of the
|
// postgres on synapse returns us precise details of the
|
||||||
// strings which actually got matched for highlighting.
|
// strings which actually got matched for highlighting.
|
||||||
// for overlapping highlights, favour longer (more specific) terms first
|
|
||||||
highlights = data.search_categories.room_events.highlights
|
// combine the highlight list with our existing list; build an object
|
||||||
.sort(function(a, b) { b.length - a.length });
|
// to avoid O(N^2) fail
|
||||||
}
|
var highlights = {};
|
||||||
else {
|
results.highlights.forEach(function(hl) { highlights[hl] = 1; });
|
||||||
// sqlite doesn't, so just try to highlight the literal search term
|
self.state.searchHighlights.forEach(function(hl) { highlights[hl] = 1; });
|
||||||
|
|
||||||
|
// turn it back into an ordered list. For overlapping highlights,
|
||||||
|
// favour longer (more specific) terms first
|
||||||
|
highlights = Object.keys(highlights).sort(function(a, b) { b.length - a.length });
|
||||||
|
|
||||||
|
// sqlite doesn't give us any highlights, so just try to highlight the literal search term
|
||||||
|
if (highlights.length == 0) {
|
||||||
highlights = [ term ];
|
highlights = [ term ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// append the new results to our existing results
|
||||||
|
var events = self.state.searchResults.concat(results.results);
|
||||||
|
|
||||||
self.setState({
|
self.setState({
|
||||||
highlights: highlights,
|
searchHighlights: highlights,
|
||||||
searchTerm: term,
|
searchResults: events,
|
||||||
searchResults: data,
|
searchCount: results.count,
|
||||||
searchScope: scope,
|
|
||||||
searchCount: data.search_categories.room_events.count,
|
|
||||||
});
|
});
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
@ -570,7 +560,35 @@ module.exports = React.createClass({
|
||||||
self.setState({
|
self.setState({
|
||||||
searchInProgress: false
|
searchInProgress: false
|
||||||
});
|
});
|
||||||
});
|
}).done();
|
||||||
|
},
|
||||||
|
|
||||||
|
_getSearchCondition: function(term, scope) {
|
||||||
|
var filter;
|
||||||
|
|
||||||
|
if (scope === "Room") {
|
||||||
|
filter = {
|
||||||
|
// XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :(
|
||||||
|
rooms: [
|
||||||
|
this.props.roomId
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
search_categories: {
|
||||||
|
room_events: {
|
||||||
|
search_term: term,
|
||||||
|
filter: filter,
|
||||||
|
order_by: "recent",
|
||||||
|
event_context: {
|
||||||
|
before_limit: 1,
|
||||||
|
after_limit: 1,
|
||||||
|
include_profile: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getEventTiles: function() {
|
getEventTiles: function() {
|
||||||
|
@ -584,58 +602,45 @@ module.exports = React.createClass({
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (this.state.searchResults)
|
if (this.state.searchResults)
|
||||||
{
|
|
||||||
if (!this.state.searchResults.search_categories.room_events.results ||
|
|
||||||
!this.state.searchResults.search_categories.room_events.groups)
|
|
||||||
{
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: this dance is foul, due to the results API not directly returning sorted results
|
|
||||||
var results = this.state.searchResults.search_categories.room_events.results;
|
|
||||||
var roomIdGroups = this.state.searchResults.search_categories.room_events.groups.room_id;
|
|
||||||
|
|
||||||
if (Array.isArray(results)) {
|
|
||||||
// Old search API used to return results as a event_id -> result dict, but now
|
|
||||||
// returns a straightforward list.
|
|
||||||
results = results.reduce(function(prev, curr) {
|
|
||||||
prev[curr.result.event_id] = curr;
|
|
||||||
return prev;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(roomIdGroups)
|
|
||||||
.sort(function(a, b) { roomIdGroups[a].order - roomIdGroups[b].order }) // WHY NOT RETURN AN ORDERED ARRAY?!?!?!
|
|
||||||
.forEach(function(roomId)
|
|
||||||
{
|
{
|
||||||
// XXX: todo: merge overlapping results somehow?
|
// XXX: todo: merge overlapping results somehow?
|
||||||
// XXX: why doesn't searching on name work?
|
// XXX: why doesn't searching on name work?
|
||||||
|
|
||||||
|
var lastRoomId;
|
||||||
|
|
||||||
|
for (var i = this.state.searchResults.length - 1; i >= 0; i--) {
|
||||||
|
var result = this.state.searchResults[i];
|
||||||
|
var mxEv = new Matrix.MatrixEvent(result.result);
|
||||||
|
|
||||||
if (self.state.searchScope === 'All') {
|
if (self.state.searchScope === 'All') {
|
||||||
ret.push(<li key={ roomId }><h1>Room: { cli.getRoom(roomId).name }</h1></li>);
|
var roomId = result.result.room_id;
|
||||||
|
if(roomId != lastRoomId) {
|
||||||
|
ret.push(<li key={mxEv.getId() + "-room"}><h1>Room: { cli.getRoom(roomId).name }</h1></li>);
|
||||||
|
lastRoomId = roomId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultList = roomIdGroups[roomId].results.map(function(eventId) { return results[eventId]; });
|
var ts1 = result.result.origin_server_ts;
|
||||||
for (var i = resultList.length - 1; i >= 0; i--) {
|
|
||||||
var ts1 = resultList[i].result.origin_server_ts;
|
|
||||||
ret.push(<li key={ts1 + "-search"}><DateSeparator ts={ts1}/></li>); // Rank: {resultList[i].rank}
|
ret.push(<li key={ts1 + "-search"}><DateSeparator ts={ts1}/></li>); // Rank: {resultList[i].rank}
|
||||||
var mxEv = new Matrix.MatrixEvent(resultList[i].result);
|
|
||||||
if (resultList[i].context.events_before[0]) {
|
if (result.context.events_before[0]) {
|
||||||
var mxEv2 = new Matrix.MatrixEvent(resultList[i].context.events_before[0]);
|
var mxEv2 = new Matrix.MatrixEvent(result.context.events_before[0]);
|
||||||
if (EventTile.haveTileForEvent(mxEv2)) {
|
if (EventTile.haveTileForEvent(mxEv2)) {
|
||||||
ret.push(<li key={mxEv.getId() + "-1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>);
|
ret.push(<li key={mxEv.getId() + "-1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EventTile.haveTileForEvent(mxEv)) {
|
if (EventTile.haveTileForEvent(mxEv)) {
|
||||||
ret.push(<li key={mxEv.getId() + "+0"}><EventTile mxEvent={mxEv} highlights={self.state.highlights}/></li>);
|
ret.push(<li key={mxEv.getId() + "+0"}><EventTile mxEvent={mxEv} highlights={self.state.searchHighlights}/></li>);
|
||||||
}
|
}
|
||||||
if (resultList[i].context.events_after[0]) {
|
|
||||||
var mxEv2 = new Matrix.MatrixEvent(resultList[i].context.events_after[0]);
|
if (result.context.events_after[0]) {
|
||||||
|
var mxEv2 = new Matrix.MatrixEvent(result.context.events_after[0]);
|
||||||
if (EventTile.haveTileForEvent(mxEv2)) {
|
if (EventTile.haveTileForEvent(mxEv2)) {
|
||||||
ret.push(<li key={mxEv.getId() + "+1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>);
|
ret.push(<li key={mxEv.getId() + "+1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,9 @@ module.exports = React.createClass({
|
||||||
// <EditableText label={this.props.room.name} initialValue={actual_name} placeHolder="Name" onValueChanged={this.onNameChange} />
|
// <EditableText label={this.props.room.name} initialValue={actual_name} placeHolder="Name" onValueChanged={this.onNameChange} />
|
||||||
|
|
||||||
var searchStatus;
|
var searchStatus;
|
||||||
if (this.props.searchInfo && this.props.searchInfo.searchTerm) {
|
// don't display the search count until the search completes and
|
||||||
|
// gives us a non-null searchCount.
|
||||||
|
if (this.props.searchInfo && this.props.searchInfo.searchCount !== null) {
|
||||||
searchStatus = <div className="mx_RoomHeader_searchStatus"> ({ this.props.searchInfo.searchCount } results)</div>;
|
searchStatus = <div className="mx_RoomHeader_searchStatus"> ({ this.props.searchInfo.searchCount } results)</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue