mirror of https://github.com/vector-im/riot-web
				
				
				
			Merge pull request #60 from matrix-org/rav/vector_search
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.pull/21833/head
						commit
						3b87d45e32
					
				|  | @ -490,75 +490,65 @@ module.exports = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     onSearch: 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 | ||||
|                 ] | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         var self = this; | ||||
|         self.setState({ | ||||
|             searchInProgress: true | ||||
|         this.setState({ | ||||
|             searchTerm: term, | ||||
|             searchScope: scope, | ||||
|             searchResults: [], | ||||
|             searchHighlights: [], | ||||
|             searchCount: null, | ||||
|         }); | ||||
| 
 | ||||
|         MatrixClientPeg.get().search({ | ||||
|             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) { | ||||
|         this._getSearchBatch(term, scope); | ||||
|     }, | ||||
| 
 | ||||
|             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"); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // for debugging:
 | ||||
|             // data.search_categories.room_events.highlights = ["hello", "everybody"];
 | ||||
|             var results = data.search_categories.room_events; | ||||
| 
 | ||||
|             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
 | ||||
|                 // strings which actually got matched for highlighting.
 | ||||
|                 // for overlapping highlights, favour longer (more specific) terms first
 | ||||
|                 highlights = data.search_categories.room_events.highlights | ||||
|                              .sort(function(a, b) { b.length - a.length }); | ||||
|             } | ||||
|             else { | ||||
|                 // sqlite doesn't, so just try to highlight the literal search term
 | ||||
|             // postgres on synapse returns us precise details of the
 | ||||
|             // strings which actually got matched for highlighting.
 | ||||
| 
 | ||||
|             // combine the highlight list with our existing list; build an object
 | ||||
|             // to avoid O(N^2) fail
 | ||||
|             var highlights = {}; | ||||
|             results.highlights.forEach(function(hl) { highlights[hl] = 1; }); | ||||
|             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 ]; | ||||
|             } | ||||
| 
 | ||||
|             // append the new results to our existing results
 | ||||
|             var events = self.state.searchResults.concat(results.results); | ||||
| 
 | ||||
|             self.setState({ | ||||
|                 highlights: highlights, | ||||
|                 searchTerm: term, | ||||
|                 searchResults: data, | ||||
|                 searchScope: scope, | ||||
|                 searchCount: data.search_categories.room_events.count, | ||||
|                 searchHighlights: highlights, | ||||
|                 searchResults: events, | ||||
|                 searchCount: results.count, | ||||
|             }); | ||||
|         }, function(error) { | ||||
|             var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); | ||||
|  | @ -570,7 +560,35 @@ module.exports = React.createClass({ | |||
|             self.setState({ | ||||
|                 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() { | ||||
|  | @ -585,57 +603,44 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|         if (this.state.searchResults) | ||||
|         { | ||||
|             if (!this.state.searchResults.search_categories.room_events.results || | ||||
|                 !this.state.searchResults.search_categories.room_events.groups) | ||||
|             { | ||||
|                 return ret; | ||||
|             } | ||||
|             // XXX: todo: merge overlapping results somehow?
 | ||||
|             // XXX: why doesn't searching on name work?
 | ||||
| 
 | ||||
|             // 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; | ||||
|             var lastRoomId; | ||||
| 
 | ||||
|             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; | ||||
|                 }, {}); | ||||
|             } | ||||
|             for (var i = this.state.searchResults.length - 1; i >= 0; i--) { | ||||
|                 var result = this.state.searchResults[i]; | ||||
|                 var mxEv = new Matrix.MatrixEvent(result.result); | ||||
| 
 | ||||
|             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: why doesn't searching on name work?
 | ||||
|                 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]; }); | ||||
|                 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} | ||||
|                     var mxEv = new Matrix.MatrixEvent(resultList[i].result); | ||||
|                     if (resultList[i].context.events_before[0]) { | ||||
|                         var mxEv2 = new Matrix.MatrixEvent(resultList[i].context.events_before[0]); | ||||
|                         if (EventTile.haveTileForEvent(mxEv2)) { | ||||
|                             ret.push(<li key={mxEv.getId() + "-1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>); | ||||
|                         } | ||||
|                     } | ||||
|                     if (EventTile.haveTileForEvent(mxEv)) { | ||||
|                         ret.push(<li key={mxEv.getId() + "+0"}><EventTile mxEvent={mxEv} highlights={self.state.highlights}/></li>); | ||||
|                     } | ||||
|                     if (resultList[i].context.events_after[0]) { | ||||
|                         var mxEv2 = new Matrix.MatrixEvent(resultList[i].context.events_after[0]); | ||||
|                         if (EventTile.haveTileForEvent(mxEv2)) { | ||||
|                             ret.push(<li key={mxEv.getId() + "+1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>); | ||||
|                         } | ||||
|                 var ts1 = result.result.origin_server_ts; | ||||
|                 ret.push(<li key={ts1 + "-search"}><DateSeparator ts={ts1}/></li>); // Rank: {resultList[i].rank} | ||||
| 
 | ||||
|                 if (result.context.events_before[0]) { | ||||
|                     var mxEv2 = new Matrix.MatrixEvent(result.context.events_before[0]); | ||||
|                     if (EventTile.haveTileForEvent(mxEv2)) { | ||||
|                         ret.push(<li key={mxEv.getId() + "-1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|                 if (EventTile.haveTileForEvent(mxEv)) { | ||||
|                     ret.push(<li key={mxEv.getId() + "+0"}><EventTile mxEvent={mxEv} highlights={self.state.searchHighlights}/></li>); | ||||
|                 } | ||||
| 
 | ||||
|                 if (result.context.events_after[0]) { | ||||
|                     var mxEv2 = new Matrix.MatrixEvent(result.context.events_after[0]); | ||||
|                     if (EventTile.haveTileForEvent(mxEv2)) { | ||||
|                         ret.push(<li key={mxEv.getId() + "+1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             return ret; | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -103,7 +103,9 @@ module.exports = React.createClass({ | |||
|                 // <EditableText label={this.props.room.name} initialValue={actual_name} placeHolder="Name" onValueChanged={this.onNameChange} />
 | ||||
| 
 | ||||
|                 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>; | ||||
|                 } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Richard van der Hoff
						Richard van der Hoff