1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { Subscription } from 'rxjs';
4 import { retryWhen, delay, take } from 'rxjs/operators';
15 } from '../interfaces';
16 import { WebSocketService } from '../services';
20 routeSearchTypeToEnum,
23 import { PostListing } from './post-listing';
24 import { CommentNodes } from './comment-nodes';
25 import { i18n } from '../i18next';
26 import { T } from 'inferno-i18next';
28 interface SearchState {
33 searchResponse: SearchResponse;
37 export class Search extends Component<any, SearchState> {
38 private subscription: Subscription;
39 private emptyState: SearchState = {
40 q: this.getSearchQueryFromProps(this.props),
41 type_: this.getSearchTypeFromProps(this.props),
42 sort: this.getSortTypeFromProps(this.props),
43 page: this.getPageFromProps(this.props),
55 getSearchQueryFromProps(props: any): string {
56 return props.match.params.q ? props.match.params.q : '';
59 getSearchTypeFromProps(props: any): SearchType {
60 return props.match.params.type
61 ? routeSearchTypeToEnum(props.match.params.type)
65 getSortTypeFromProps(props: any): SortType {
66 return props.match.params.sort
67 ? routeSortTypeToEnum(props.match.params.sort)
71 getPageFromProps(props: any): number {
72 return props.match.params.page ? Number(props.match.params.page) : 1;
75 constructor(props: any, context: any) {
76 super(props, context);
78 this.state = this.emptyState;
80 this.subscription = WebSocketService.Instance.subject
90 msg => this.parseMessage(msg),
91 err => console.error(err),
92 () => console.log('complete')
100 componentWillUnmount() {
101 this.subscription.unsubscribe();
104 // Necessary for back button for some reason
105 componentWillReceiveProps(nextProps: any) {
107 nextProps.history.action == 'POP' ||
108 nextProps.history.action == 'PUSH'
110 this.state = this.emptyState;
111 this.state.q = this.getSearchQueryFromProps(nextProps);
112 this.state.type_ = this.getSearchTypeFromProps(nextProps);
113 this.state.sort = this.getSortTypeFromProps(nextProps);
114 this.state.page = this.getPageFromProps(nextProps);
115 this.setState(this.state);
120 componentDidMount() {
121 document.title = `${i18n.t('search')} - ${
122 WebSocketService.Instance.site.name
128 <div class="container">
132 <T i18nKey="search">#</T>
136 {this.state.type_ == SearchType.All && this.all()}
137 {this.state.type_ == SearchType.Comments && this.comments()}
138 {this.state.type_ == SearchType.Posts && this.posts()}
139 {this.state.type_ == SearchType.Communities && this.communities()}
140 {this.state.type_ == SearchType.Users && this.users()}
153 onSubmit={linkEvent(this, this.handleSearchSubmit)}
157 class="form-control mr-2"
159 placeholder={`${i18n.t('search')}...`}
160 onInput={linkEvent(this, this.handleQChange)}
164 <button type="submit" class="btn btn-secondary mr-2">
165 {this.state.loading ? (
166 <svg class="icon icon-spinner spin">
167 <use xlinkHref="#icon-spinner"></use>
171 <T i18nKey="search">#</T>
181 <div className="mb-2">
183 value={this.state.type_}
184 onChange={linkEvent(this, this.handleTypeChange)}
185 class="custom-select custom-select-sm w-auto"
188 <T i18nKey="type">#</T>
190 <option value={SearchType.All}>
191 <T i18nKey="all">#</T>
193 <option value={SearchType.Comments}>
194 <T i18nKey="comments">#</T>
196 <option value={SearchType.Posts}>
197 <T i18nKey="posts">#</T>
199 <option value={SearchType.Communities}>
200 <T i18nKey="communities">#</T>
202 <option value={SearchType.Users}>
203 <T i18nKey="users">#</T>
207 value={this.state.sort}
208 onChange={linkEvent(this, this.handleSortChange)}
209 class="custom-select custom-select-sm w-auto ml-2"
212 <T i18nKey="sort_type">#</T>
214 <option value={SortType.New}>
215 <T i18nKey="new">#</T>
217 <option value={SortType.TopDay}>
218 <T i18nKey="top_day">#</T>
220 <option value={SortType.TopWeek}>
221 <T i18nKey="week">#</T>
223 <option value={SortType.TopMonth}>
224 <T i18nKey="month">#</T>
226 <option value={SortType.TopYear}>
227 <T i18nKey="year">#</T>
229 <option value={SortType.TopAll}>
230 <T i18nKey="all">#</T>
238 let combined: Array<{
240 data: Comment | Post | Community | UserView;
242 let comments = this.state.searchResponse.comments.map(e => {
243 return { type_: 'comments', data: e };
245 let posts = this.state.searchResponse.posts.map(e => {
246 return { type_: 'posts', data: e };
248 let communities = this.state.searchResponse.communities.map(e => {
249 return { type_: 'communities', data: e };
251 let users = this.state.searchResponse.users.map(e => {
252 return { type_: 'users', data: e };
255 combined.push(...comments);
256 combined.push(...posts);
257 combined.push(...communities);
258 combined.push(...users);
261 if (this.state.sort == SortType.New) {
262 combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
266 ((b.data as Comment | Post).score |
267 (b.data as Community).number_of_subscribers |
268 (b.data as UserView).comment_score) -
269 ((a.data as Comment | Post).score |
270 (a.data as Community).number_of_subscribers |
271 (a.data as UserView).comment_score)
279 {i.type_ == 'posts' && (
280 <PostListing post={i.data as Post} showCommunity viewOnly />
282 {i.type_ == 'comments' && (
284 nodes={[{ comment: i.data as Comment }]}
289 {i.type_ == 'communities' && (
292 <Link to={`/c/${(i.data as Community).name}`}>{`/c/${
293 (i.data as Community).name
296 <span>{` - ${(i.data as Community).title} - ${
297 (i.data as Community).number_of_subscribers
298 } subscribers`}</span>
301 {i.type_ == 'users' && (
305 className="text-info"
306 to={`/u/${(i.data as UserView).name}`}
307 >{`/u/${(i.data as UserView).name}`}</Link>
310 (i.data as UserView).comment_score
311 } comment karma`}</span>
323 {this.state.searchResponse.comments.map(comment => (
324 <CommentNodes nodes={[{ comment: comment }]} noIndent viewOnly />
333 {this.state.searchResponse.posts.map(post => (
334 <PostListing post={post} showCommunity viewOnly />
340 // Todo possibly create UserListing and CommunityListing
344 {this.state.searchResponse.communities.map(community => (
347 <Link to={`/c/${community.name}`}>{`/c/${community.name}`}</Link>
349 <span>{` - ${community.title} - ${community.number_of_subscribers} subscribers`}</span>
359 {this.state.searchResponse.users.map(user => (
363 className="text-info"
364 to={`/u/${user.name}`}
365 >{`/u/${user.name}`}</Link>
367 <span>{` - ${user.comment_score} comment karma`}</span>
377 {this.state.page > 1 && (
379 class="btn btn-sm btn-secondary mr-1"
380 onClick={linkEvent(this, this.prevPage)}
382 <T i18nKey="prev">#</T>
386 class="btn btn-sm btn-secondary"
387 onClick={linkEvent(this, this.nextPage)}
389 <T i18nKey="next">#</T>
396 let res = this.state.searchResponse;
401 res.posts.length == 0 &&
402 res.comments.length == 0 &&
403 res.communities.length == 0 &&
404 res.users.length == 0 && (
406 <T i18nKey="no_results">#</T>
413 nextPage(i: Search) {
420 prevPage(i: Search) {
428 let form: SearchForm = {
430 type_: SearchType[this.state.type_],
431 sort: SortType[this.state.sort],
432 page: this.state.page,
436 if (this.state.q != '') {
437 WebSocketService.Instance.search(form);
441 handleSortChange(i: Search, event: any) {
442 i.state.sort = Number(event.target.value);
448 handleTypeChange(i: Search, event: any) {
449 i.state.type_ = Number(event.target.value);
455 handleSearchSubmit(i: Search, event: any) {
456 event.preventDefault();
457 i.state.loading = true;
463 handleQChange(i: Search, event: any) {
464 i.state.q = event.target.value;
469 let typeStr = SearchType[this.state.type_].toLowerCase();
470 let sortStr = SortType[this.state.sort].toLowerCase();
471 this.props.history.push(
472 `/search/q/${this.state.q}/type/${typeStr}/sort/${sortStr}/page/${this.state.page}`
476 parseMessage(msg: any) {
478 let op: UserOperation = msgOp(msg);
480 alert(i18n.t(msg.error));
482 } else if (op == UserOperation.Search) {
483 let res: SearchResponse = msg;
484 this.state.searchResponse = res;
485 this.state.loading = false;
486 document.title = `${i18n.t('search')} - ${this.state.q} - ${
487 WebSocketService.Instance.site.name
489 window.scrollTo(0, 0);
490 this.setState(this.state);