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';
17 WebSocketJsonResponse,
20 } from '../interfaces';
21 import { WebSocketService } from '../services';
25 routeSearchTypeToEnum,
29 createPostLikeFindRes,
32 import { PostListing } from './post-listing';
33 import { UserListing } from './user-listing';
34 import { CommunityLink } from './community-link';
35 import { SortSelect } from './sort-select';
36 import { CommentNodes } from './comment-nodes';
37 import { i18n } from '../i18next';
39 interface SearchState {
44 searchResponse: SearchResponse;
49 export class Search extends Component<any, SearchState> {
50 private subscription: Subscription;
51 private emptyState: SearchState = {
52 q: this.getSearchQueryFromProps(this.props),
53 type_: this.getSearchTypeFromProps(this.props),
54 sort: this.getSortTypeFromProps(this.props),
55 page: this.getPageFromProps(this.props),
67 creator_id: undefined,
69 creator_name: undefined,
70 number_of_users: undefined,
71 number_of_posts: undefined,
72 number_of_comments: undefined,
73 number_of_communities: undefined,
74 enable_downvotes: undefined,
75 open_registration: undefined,
76 enable_nsfw: undefined,
80 getSearchQueryFromProps(props: any): string {
81 return props.match.params.q ? props.match.params.q : '';
84 getSearchTypeFromProps(props: any): SearchType {
85 return props.match.params.type
86 ? routeSearchTypeToEnum(props.match.params.type)
90 getSortTypeFromProps(props: any): SortType {
91 return props.match.params.sort
92 ? routeSortTypeToEnum(props.match.params.sort)
96 getPageFromProps(props: any): number {
97 return props.match.params.page ? Number(props.match.params.page) : 1;
100 constructor(props: any, context: any) {
101 super(props, context);
103 this.state = this.emptyState;
104 this.handleSortChange = this.handleSortChange.bind(this);
106 this.subscription = WebSocketService.Instance.subject
107 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
109 msg => this.parseMessage(msg),
110 err => console.error(err),
111 () => console.log('complete')
114 WebSocketService.Instance.getSite();
121 componentWillUnmount() {
122 this.subscription.unsubscribe();
125 // Necessary for back button for some reason
126 componentWillReceiveProps(nextProps: any) {
128 nextProps.history.action == 'POP' ||
129 nextProps.history.action == 'PUSH'
131 this.state.q = this.getSearchQueryFromProps(nextProps);
132 this.state.type_ = this.getSearchTypeFromProps(nextProps);
133 this.state.sort = this.getSortTypeFromProps(nextProps);
134 this.state.page = this.getPageFromProps(nextProps);
135 this.setState(this.state);
142 <div class="container">
143 <h5>{i18n.t('search')}</h5>
146 {this.state.type_ == SearchType.All && this.all()}
147 {this.state.type_ == SearchType.Comments && this.comments()}
148 {this.state.type_ == SearchType.Posts && this.posts()}
149 {this.state.type_ == SearchType.Communities && this.communities()}
150 {this.state.type_ == SearchType.Users && this.users()}
151 {this.resultsCount() == 0 && <span>{i18n.t('no_results')}</span>}
161 onSubmit={linkEvent(this, this.handleSearchSubmit)}
165 class="form-control mr-2"
167 placeholder={`${i18n.t('search')}...`}
168 onInput={linkEvent(this, this.handleQChange)}
172 <button type="submit" class="btn btn-secondary mr-2">
173 {this.state.loading ? (
174 <svg class="icon icon-spinner spin">
175 <use xlinkHref="#icon-spinner"></use>
178 <span>{i18n.t('search')}</span>
187 <div className="mb-2">
189 value={this.state.type_}
190 onChange={linkEvent(this, this.handleTypeChange)}
191 class="custom-select custom-select-sm w-auto"
193 <option disabled>{i18n.t('type')}</option>
194 <option value={SearchType.All}>{i18n.t('all')}</option>
195 <option value={SearchType.Comments}>{i18n.t('comments')}</option>
196 <option value={SearchType.Posts}>{i18n.t('posts')}</option>
197 <option value={SearchType.Communities}>
198 {i18n.t('communities')}
200 <option value={SearchType.Users}>{i18n.t('users')}</option>
204 sort={this.state.sort}
205 onChange={this.handleSortChange}
214 let combined: Array<{
216 data: Comment | Post | Community | UserView;
218 let comments = this.state.searchResponse.comments.map(e => {
219 return { type_: 'comments', data: e };
221 let posts = this.state.searchResponse.posts.map(e => {
222 return { type_: 'posts', data: e };
224 let communities = this.state.searchResponse.communities.map(e => {
225 return { type_: 'communities', data: e };
227 let users = this.state.searchResponse.users.map(e => {
228 return { type_: 'users', data: e };
231 combined.push(...comments);
232 combined.push(...posts);
233 combined.push(...communities);
234 combined.push(...users);
237 if (this.state.sort == SortType.New) {
238 combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
242 ((b.data as Comment | Post).score |
243 (b.data as Community).number_of_subscribers |
244 (b.data as UserView).comment_score) -
245 ((a.data as Comment | Post).score |
246 (a.data as Community).number_of_subscribers |
247 (a.data as UserView).comment_score)
256 {i.type_ == 'posts' && (
258 post={i.data as Post}
260 enableDownvotes={this.state.site.enable_downvotes}
261 enableNsfw={this.state.site.enable_nsfw}
264 {i.type_ == 'comments' && (
266 nodes={[{ comment: i.data as Comment }]}
269 enableDownvotes={this.state.site.enable_downvotes}
272 {i.type_ == 'communities' && (
273 <div>{this.communityListing(i.data as Community)}</div>
275 {i.type_ == 'users' && (
280 name: (i.data as UserView).name,
281 avatar: (i.data as UserView).avatar,
286 (i.data as UserView).comment_score
287 } comment karma`}</span>
300 nodes={commentsToFlatNodes(this.state.searchResponse.comments)}
303 enableDownvotes={this.state.site.enable_downvotes}
311 {this.state.searchResponse.posts.map(post => (
317 enableDownvotes={this.state.site.enable_downvotes}
318 enableNsfw={this.state.site.enable_nsfw}
327 // Todo possibly create UserListing and CommunityListing
331 {this.state.searchResponse.communities.map(community => (
333 <div class="col-12">{this.communityListing(community)}</div>
340 communityListing(community: Community) {
344 <CommunityLink community={community} />
346 <span>{` - ${community.title} -
347 ${i18n.t('number_of_subscribers', {
348 count: community.number_of_subscribers,
358 {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>
378 {this.state.page > 1 && (
380 class="btn btn-sm btn-secondary mr-1"
381 onClick={linkEvent(this, this.prevPage)}
387 {this.resultsCount() > 0 && (
389 class="btn btn-sm btn-secondary"
390 onClick={linkEvent(this, this.nextPage)}
399 resultsCount(): number {
400 let res = this.state.searchResponse;
403 res.comments.length +
404 res.communities.length +
409 nextPage(i: Search) {
416 prevPage(i: Search) {
424 let form: SearchForm = {
426 type_: SearchType[this.state.type_],
427 sort: SortType[this.state.sort],
428 page: this.state.page,
432 if (this.state.q != '') {
433 WebSocketService.Instance.search(form);
437 handleSortChange(val: SortType) {
438 this.state.sort = val;
440 this.setState(this.state);
444 handleTypeChange(i: Search, event: any) {
445 i.state.type_ = Number(event.target.value);
451 handleSearchSubmit(i: Search, event: any) {
452 event.preventDefault();
453 i.state.loading = true;
459 handleQChange(i: Search, event: any) {
460 i.state.q = event.target.value;
465 let typeStr = SearchType[this.state.type_].toLowerCase();
466 let sortStr = SortType[this.state.sort].toLowerCase();
467 this.props.history.push(
468 `/search/q/${this.state.q}/type/${typeStr}/sort/${sortStr}/page/${this.state.page}`
472 parseMessage(msg: WebSocketJsonResponse) {
474 let res = wsJsonToRes(msg);
476 toast(i18n.t(msg.error), 'danger');
478 } else if (res.op == UserOperation.Search) {
479 let data = res.data as SearchResponse;
480 this.state.searchResponse = data;
481 this.state.loading = false;
482 document.title = `${i18n.t('search')} - ${this.state.q} - ${
485 window.scrollTo(0, 0);
486 this.setState(this.state);
487 } else if (res.op == UserOperation.CreateCommentLike) {
488 let data = res.data as CommentResponse;
489 createCommentLikeRes(data, this.state.searchResponse.comments);
490 this.setState(this.state);
491 } else if (res.op == UserOperation.CreatePostLike) {
492 let data = res.data as PostResponse;
493 createPostLikeFindRes(data, this.state.searchResponse.posts);
494 this.setState(this.state);
495 } else if (res.op == UserOperation.GetSite) {
496 let data = res.data as GetSiteResponse;
497 this.state.site = data.site;
498 this.setState(this.state);
499 document.title = `${i18n.t('search')} - ${data.site.name}`;