]> Untitled Git - lemmy-ui.git/blob - src/shared/components/search.tsx
Integrating resolve_user into search. (#377)
[lemmy-ui.git] / src / shared / components / search.tsx
1 import { Component, linkEvent } from "inferno";
2 import {
3   CommentResponse,
4   CommentView,
5   CommunityView,
6   GetCommunity,
7   GetPersonDetails,
8   ListCommunities,
9   ListCommunitiesResponse,
10   ListingType,
11   PersonViewSafe,
12   PostResponse,
13   PostView,
14   ResolveObject,
15   ResolveObjectResponse,
16   Search as SearchForm,
17   SearchResponse,
18   SearchType,
19   Site,
20   SortType,
21   UserOperation,
22 } from "lemmy-js-client";
23 import { Subscription } from "rxjs";
24 import { InitialFetchRequest } from "shared/interfaces";
25 import { i18n } from "../i18next";
26 import { WebSocketService } from "../services";
27 import {
28   authField,
29   capitalizeFirstLetter,
30   choicesConfig,
31   commentsToFlatNodes,
32   communitySelectName,
33   communityToChoice,
34   createCommentLikeRes,
35   createPostLikeFindRes,
36   debounce,
37   fetchCommunities,
38   fetchLimit,
39   fetchUsers,
40   isBrowser,
41   personSelectName,
42   personToChoice,
43   restoreScrollPosition,
44   routeListingTypeToEnum,
45   routeSearchTypeToEnum,
46   routeSortTypeToEnum,
47   saveScrollPosition,
48   setIsoData,
49   setOptionalAuth,
50   showLocal,
51   toast,
52   wsClient,
53   wsJsonToRes,
54   wsSubscribe,
55   wsUserOp,
56 } from "../utils";
57 import { CommentNodes } from "./comment/comment-nodes";
58 import { HtmlTags } from "./common/html-tags";
59 import { Spinner } from "./common/icon";
60 import { ListingTypeSelect } from "./common/listing-type-select";
61 import { Paginator } from "./common/paginator";
62 import { SortSelect } from "./common/sort-select";
63 import { CommunityLink } from "./community/community-link";
64 import { PersonListing } from "./person/person-listing";
65 import { PostListing } from "./post/post-listing";
66
67 var Choices: any;
68 if (isBrowser()) {
69   Choices = require("choices.js");
70 }
71
72 interface SearchProps {
73   q: string;
74   type_: SearchType;
75   sort: SortType;
76   listingType: ListingType;
77   communityId: number;
78   creatorId: number;
79   page: number;
80 }
81
82 interface SearchState {
83   q: string;
84   type_: SearchType;
85   sort: SortType;
86   listingType: ListingType;
87   communityId: number;
88   creatorId: number;
89   page: number;
90   searchResponse: SearchResponse;
91   communities: CommunityView[];
92   creator?: PersonViewSafe;
93   loading: boolean;
94   site: Site;
95   searchText: string;
96   resolveObjectResponse: ResolveObjectResponse;
97 }
98
99 interface UrlParams {
100   q?: string;
101   type_?: SearchType;
102   sort?: SortType;
103   listingType?: ListingType;
104   communityId?: number;
105   creatorId?: number;
106   page?: number;
107 }
108
109 interface Combined {
110   type_: string;
111   data: CommentView | PostView | CommunityView | PersonViewSafe;
112   published: string;
113 }
114
115 export class Search extends Component<any, SearchState> {
116   private isoData = setIsoData(this.context);
117   private communityChoices: any;
118   private creatorChoices: any;
119   private subscription: Subscription;
120   private emptyState: SearchState = {
121     q: Search.getSearchQueryFromProps(this.props.match.params.q),
122     type_: Search.getSearchTypeFromProps(this.props.match.params.type),
123     sort: Search.getSortTypeFromProps(this.props.match.params.sort),
124     listingType: Search.getListingTypeFromProps(
125       this.props.match.params.listing_type
126     ),
127     page: Search.getPageFromProps(this.props.match.params.page),
128     searchText: Search.getSearchQueryFromProps(this.props.match.params.q),
129     communityId: Search.getCommunityIdFromProps(
130       this.props.match.params.community_id
131     ),
132     creatorId: Search.getCreatorIdFromProps(this.props.match.params.creator_id),
133     searchResponse: {
134       type_: null,
135       posts: [],
136       comments: [],
137       communities: [],
138       users: [],
139     },
140     resolveObjectResponse: {
141       comment: null,
142       post: null,
143       community: null,
144       person: null,
145     },
146     loading: true,
147     site: this.isoData.site_res.site_view.site,
148     communities: [],
149   };
150
151   static getSearchQueryFromProps(q: string): string {
152     return decodeURIComponent(q) || "";
153   }
154
155   static getSearchTypeFromProps(type_: string): SearchType {
156     return type_ ? routeSearchTypeToEnum(type_) : SearchType.All;
157   }
158
159   static getSortTypeFromProps(sort: string): SortType {
160     return sort ? routeSortTypeToEnum(sort) : SortType.TopAll;
161   }
162
163   static getListingTypeFromProps(listingType: string): ListingType {
164     return listingType ? routeListingTypeToEnum(listingType) : ListingType.All;
165   }
166
167   static getCommunityIdFromProps(id: string): number {
168     return id ? Number(id) : 0;
169   }
170
171   static getCreatorIdFromProps(id: string): number {
172     return id ? Number(id) : 0;
173   }
174
175   static getPageFromProps(page: string): number {
176     return page ? Number(page) : 1;
177   }
178
179   constructor(props: any, context: any) {
180     super(props, context);
181
182     this.state = this.emptyState;
183     this.handleSortChange = this.handleSortChange.bind(this);
184     this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
185     this.handlePageChange = this.handlePageChange.bind(this);
186
187     this.parseMessage = this.parseMessage.bind(this);
188     this.subscription = wsSubscribe(this.parseMessage);
189
190     // Only fetch the data if coming from another route
191     if (this.isoData.path == this.context.router.route.match.url) {
192       let singleOrMultipleCommunities = this.isoData.routeData[0];
193       if (singleOrMultipleCommunities.communities) {
194         this.state.communities = this.isoData.routeData[0].communities;
195       } else {
196         this.state.communities = [this.isoData.routeData[0].community_view];
197       }
198
199       let creator = this.isoData.routeData[1];
200       if (creator?.person_view) {
201         this.state.creator = this.isoData.routeData[1].person_view;
202       }
203       if (this.state.q != "") {
204         this.state.searchResponse = this.isoData.routeData[2];
205         this.state.resolveObjectResponse = this.isoData.routeData[3];
206         this.state.loading = false;
207       } else {
208         this.search();
209       }
210     } else {
211       this.fetchCommunities();
212       this.search();
213     }
214   }
215
216   componentWillUnmount() {
217     this.subscription.unsubscribe();
218     saveScrollPosition(this.context);
219   }
220
221   componentDidMount() {
222     this.setupCommunityFilter();
223     this.setupCreatorFilter();
224   }
225
226   static getDerivedStateFromProps(props: any): SearchProps {
227     return {
228       q: Search.getSearchQueryFromProps(props.match.params.q),
229       type_: Search.getSearchTypeFromProps(props.match.params.type),
230       sort: Search.getSortTypeFromProps(props.match.params.sort),
231       listingType: Search.getListingTypeFromProps(
232         props.match.params.listing_type
233       ),
234       communityId: Search.getCommunityIdFromProps(
235         props.match.params.community_id
236       ),
237       creatorId: Search.getCreatorIdFromProps(props.match.params.creator_id),
238       page: Search.getPageFromProps(props.match.params.page),
239     };
240   }
241
242   fetchCommunities() {
243     let listCommunitiesForm: ListCommunities = {
244       type_: ListingType.All,
245       sort: SortType.TopAll,
246       limit: fetchLimit,
247       auth: authField(false),
248     };
249     WebSocketService.Instance.send(
250       wsClient.listCommunities(listCommunitiesForm)
251     );
252   }
253
254   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
255     let pathSplit = req.path.split("/");
256     let promises: Promise<any>[] = [];
257
258     let communityId = this.getCommunityIdFromProps(pathSplit[11]);
259     if (communityId !== 0) {
260       let getCommunityForm: GetCommunity = {
261         id: communityId,
262       };
263       setOptionalAuth(getCommunityForm, req.auth);
264       promises.push(req.client.getCommunity(getCommunityForm));
265     } else {
266       let listCommunitiesForm: ListCommunities = {
267         type_: ListingType.All,
268         sort: SortType.TopAll,
269         limit: fetchLimit,
270       };
271       setOptionalAuth(listCommunitiesForm, req.auth);
272       promises.push(req.client.listCommunities(listCommunitiesForm));
273     }
274
275     let creatorId = this.getCreatorIdFromProps(pathSplit[13]);
276     if (creatorId !== 0) {
277       let getCreatorForm: GetPersonDetails = {
278         person_id: creatorId,
279       };
280       setOptionalAuth(getCreatorForm, req.auth);
281       promises.push(req.client.getPersonDetails(getCreatorForm));
282     } else {
283       promises.push(Promise.resolve());
284     }
285
286     let form: SearchForm = {
287       q: this.getSearchQueryFromProps(pathSplit[3]),
288       type_: this.getSearchTypeFromProps(pathSplit[5]),
289       sort: this.getSortTypeFromProps(pathSplit[7]),
290       listing_type: this.getListingTypeFromProps(pathSplit[9]),
291       page: this.getPageFromProps(pathSplit[15]),
292       limit: fetchLimit,
293     };
294     if (communityId !== 0) {
295       form.community_id = communityId;
296     }
297     if (creatorId !== 0) {
298       form.creator_id = creatorId;
299     }
300     setOptionalAuth(form, req.auth);
301
302     let resolveObjectForm: ResolveObject = {
303       q: this.getSearchQueryFromProps(pathSplit[3]),
304     };
305     setOptionalAuth(resolveObjectForm, req.auth);
306
307     if (form.q != "") {
308       promises.push(req.client.search(form));
309       promises.push(req.client.resolveObject(resolveObjectForm));
310     }
311
312     return promises;
313   }
314
315   componentDidUpdate(_: any, lastState: SearchState) {
316     if (
317       lastState.q !== this.state.q ||
318       lastState.type_ !== this.state.type_ ||
319       lastState.sort !== this.state.sort ||
320       lastState.listingType !== this.state.listingType ||
321       lastState.communityId !== this.state.communityId ||
322       lastState.creatorId !== this.state.creatorId ||
323       lastState.page !== this.state.page
324     ) {
325       this.setState({ loading: true, searchText: this.state.q });
326       this.search();
327     }
328   }
329
330   get documentTitle(): string {
331     if (this.state.q) {
332       return `${i18n.t("search")} - ${this.state.q} - ${this.state.site.name}`;
333     } else {
334       return `${i18n.t("search")} - ${this.state.site.name}`;
335     }
336   }
337
338   render() {
339     return (
340       <div class="container">
341         <HtmlTags
342           title={this.documentTitle}
343           path={this.context.router.route.match.url}
344         />
345         <h5>{i18n.t("search")}</h5>
346         {this.selects()}
347         {this.searchForm()}
348         {this.state.type_ == SearchType.All && this.all()}
349         {this.state.type_ == SearchType.Comments && this.comments()}
350         {this.state.type_ == SearchType.Posts && this.posts()}
351         {this.state.type_ == SearchType.Communities && this.communities()}
352         {this.state.type_ == SearchType.Users && this.users()}
353         {this.state.type_ == SearchType.Url && this.posts()}
354         {this.resultsCount() == 0 && <span>{i18n.t("no_results")}</span>}
355         <Paginator page={this.state.page} onChange={this.handlePageChange} />
356       </div>
357     );
358   }
359
360   searchForm() {
361     return (
362       <form
363         class="form-inline"
364         onSubmit={linkEvent(this, this.handleSearchSubmit)}
365       >
366         <input
367           type="text"
368           class="form-control mr-2 mb-2"
369           value={this.state.searchText}
370           placeholder={`${i18n.t("search")}...`}
371           aria-label={i18n.t("search")}
372           onInput={linkEvent(this, this.handleQChange)}
373           required
374           minLength={3}
375         />
376         <button type="submit" class="btn btn-secondary mr-2 mb-2">
377           {this.state.loading ? <Spinner /> : <span>{i18n.t("search")}</span>}
378         </button>
379       </form>
380     );
381   }
382
383   selects() {
384     return (
385       <div className="mb-2">
386         <select
387           value={this.state.type_}
388           onChange={linkEvent(this, this.handleTypeChange)}
389           class="custom-select w-auto mb-2"
390           aria-label={i18n.t("type")}
391         >
392           <option disabled aria-hidden="true">
393             {i18n.t("type")}
394           </option>
395           <option value={SearchType.All}>{i18n.t("all")}</option>
396           <option value={SearchType.Comments}>{i18n.t("comments")}</option>
397           <option value={SearchType.Posts}>{i18n.t("posts")}</option>
398           <option value={SearchType.Communities}>
399             {i18n.t("communities")}
400           </option>
401           <option value={SearchType.Users}>{i18n.t("users")}</option>
402           <option value={SearchType.Url}>{i18n.t("url")}</option>
403         </select>
404         <span class="ml-2">
405           <ListingTypeSelect
406             type_={this.state.listingType}
407             showLocal={showLocal(this.isoData)}
408             onChange={this.handleListingTypeChange}
409           />
410         </span>
411         <span class="ml-2">
412           <SortSelect
413             sort={this.state.sort}
414             onChange={this.handleSortChange}
415             hideHot
416             hideMostComments
417           />
418         </span>
419         <div class="form-row">
420           {this.state.communities.length > 0 && this.communityFilter()}
421           {this.creatorFilter()}
422         </div>
423       </div>
424     );
425   }
426
427   postViewToCombined(postView: PostView): Combined {
428     return {
429       type_: "posts",
430       data: postView,
431       published: postView.post.published,
432     };
433   }
434
435   commentViewToCombined(commentView: CommentView): Combined {
436     return {
437       type_: "comments",
438       data: commentView,
439       published: commentView.comment.published,
440     };
441   }
442
443   communityViewToCombined(communityView: CommunityView): Combined {
444     return {
445       type_: "communities",
446       data: communityView,
447       published: communityView.community.published,
448     };
449   }
450
451   personViewSafeToCombined(personViewSafe: PersonViewSafe): Combined {
452     return {
453       type_: "users",
454       data: personViewSafe,
455       published: personViewSafe.person.published,
456     };
457   }
458
459   buildCombined(): Combined[] {
460     let combined: Combined[] = [];
461
462     // Push the possible resolve / federated objects first
463     let resolveComment = this.state.resolveObjectResponse.comment;
464     if (resolveComment) {
465       combined.push(this.commentViewToCombined(resolveComment));
466     }
467     let resolvePost = this.state.resolveObjectResponse.post;
468     if (resolvePost) {
469       combined.push(this.postViewToCombined(resolvePost));
470     }
471     let resolveCommunity = this.state.resolveObjectResponse.community;
472     if (resolveCommunity) {
473       combined.push(this.communityViewToCombined(resolveCommunity));
474     }
475     let resolveUser = this.state.resolveObjectResponse.person;
476     if (resolveUser) {
477       combined.push(this.personViewSafeToCombined(resolveUser));
478     }
479
480     // Push the search results
481     combined.push(
482       ...this.state.searchResponse.comments.map(e =>
483         this.commentViewToCombined(e)
484       )
485     );
486     combined.push(
487       ...this.state.searchResponse.posts.map(e => this.postViewToCombined(e))
488     );
489     combined.push(
490       ...this.state.searchResponse.communities.map(e =>
491         this.communityViewToCombined(e)
492       )
493     );
494     combined.push(
495       ...this.state.searchResponse.users.map(e =>
496         this.personViewSafeToCombined(e)
497       )
498     );
499
500     // Sort it
501     if (this.state.sort == SortType.New) {
502       combined.sort((a, b) => b.published.localeCompare(a.published));
503     } else {
504       combined.sort(
505         (a, b) =>
506           ((b.data as CommentView | PostView).counts.score |
507             (b.data as CommunityView).counts.subscribers |
508             (b.data as PersonViewSafe).counts.comment_score) -
509           ((a.data as CommentView | PostView).counts.score |
510             (a.data as CommunityView).counts.subscribers |
511             (a.data as PersonViewSafe).counts.comment_score)
512       );
513     }
514     return combined;
515   }
516
517   all() {
518     let combined = this.buildCombined();
519     return (
520       <div>
521         {combined.map(i => (
522           <div class="row">
523             <div class="col-12">
524               {i.type_ == "posts" && (
525                 <PostListing
526                   key={(i.data as PostView).post.id}
527                   post_view={i.data as PostView}
528                   showCommunity
529                   enableDownvotes={this.state.site.enable_downvotes}
530                   enableNsfw={this.state.site.enable_nsfw}
531                 />
532               )}
533               {i.type_ == "comments" && (
534                 <CommentNodes
535                   key={(i.data as CommentView).comment.id}
536                   nodes={[{ comment_view: i.data as CommentView }]}
537                   locked
538                   noIndent
539                   enableDownvotes={this.state.site.enable_downvotes}
540                 />
541               )}
542               {i.type_ == "communities" && (
543                 <div>{this.communityListing(i.data as CommunityView)}</div>
544               )}
545               {i.type_ == "users" && (
546                 <div>{this.userListing(i.data as PersonViewSafe)}</div>
547               )}
548             </div>
549           </div>
550         ))}
551       </div>
552     );
553   }
554
555   comments() {
556     let comments: CommentView[] = [];
557
558     let resolveComment = this.state.resolveObjectResponse.comment;
559     if (resolveComment) {
560       comments.push(resolveComment);
561     }
562
563     comments.push(...this.state.searchResponse.comments);
564
565     return (
566       <CommentNodes
567         nodes={commentsToFlatNodes(comments)}
568         locked
569         noIndent
570         enableDownvotes={this.state.site.enable_downvotes}
571       />
572     );
573   }
574
575   posts() {
576     let posts: PostView[] = [];
577
578     let resolvePost = this.state.resolveObjectResponse.post;
579     if (resolvePost) {
580       posts.push(resolvePost);
581     }
582
583     posts.push(...this.state.searchResponse.posts);
584
585     return (
586       <>
587         {posts.map(post => (
588           <div class="row">
589             <div class="col-12">
590               <PostListing
591                 post_view={post}
592                 showCommunity
593                 enableDownvotes={this.state.site.enable_downvotes}
594                 enableNsfw={this.state.site.enable_nsfw}
595               />
596             </div>
597           </div>
598         ))}
599       </>
600     );
601   }
602
603   communities() {
604     let communities: CommunityView[] = [];
605
606     let resolveCommunity = this.state.resolveObjectResponse.community;
607     if (resolveCommunity) {
608       communities.push(resolveCommunity);
609     }
610
611     communities.push(...this.state.searchResponse.communities);
612     return (
613       <>
614         {communities.map(community => (
615           <div class="row">
616             <div class="col-12">{this.communityListing(community)}</div>
617           </div>
618         ))}
619       </>
620     );
621   }
622
623   users() {
624     let users: PersonViewSafe[] = [];
625
626     let resolveUser = this.state.resolveObjectResponse.person;
627     if (resolveUser) {
628       users.push(resolveUser);
629     }
630
631     users.push(...this.state.searchResponse.users);
632     return (
633       <>
634         {users.map(user => (
635           <div class="row">
636             <div class="col-12">{this.userListing(user)}</div>
637           </div>
638         ))}
639       </>
640     );
641   }
642
643   communityListing(community_view: CommunityView) {
644     return (
645       <>
646         <span>
647           <CommunityLink community={community_view.community} />
648         </span>
649         <span>{` -
650         ${i18n.t("number_of_subscribers", {
651           count: community_view.counts.subscribers,
652         })}
653       `}</span>
654       </>
655     );
656   }
657
658   userListing(person_view: PersonViewSafe) {
659     return [
660       <span>
661         <PersonListing person={person_view.person} showApubName />
662       </span>,
663       <span>{` - ${i18n.t("number_of_comments", {
664         count: person_view.counts.comment_count,
665       })}`}</span>,
666     ];
667   }
668
669   communityFilter() {
670     return (
671       <div class="form-group col-sm-6">
672         <label class="col-form-label" htmlFor="community-filter">
673           {i18n.t("community")}
674         </label>
675         <div>
676           <select
677             class="form-control"
678             id="community-filter"
679             value={this.state.communityId}
680           >
681             <option value="0">{i18n.t("all")}</option>
682             {this.state.communities.map(cv => (
683               <option value={cv.community.id}>{communitySelectName(cv)}</option>
684             ))}
685           </select>
686         </div>
687       </div>
688     );
689   }
690
691   creatorFilter() {
692     return (
693       <div class="form-group col-sm-6">
694         <label class="col-form-label" htmlFor="creator-filter">
695           {capitalizeFirstLetter(i18n.t("creator"))}
696         </label>
697         <div>
698           <select
699             class="form-control"
700             id="creator-filter"
701             value={this.state.creatorId}
702           >
703             <option value="0">{i18n.t("all")}</option>
704             {this.state.creator && (
705               <option value={this.state.creator.person.id}>
706                 {personSelectName(this.state.creator)}
707               </option>
708             )}
709           </select>
710         </div>
711       </div>
712     );
713   }
714
715   resultsCount(): number {
716     let res = this.state.searchResponse;
717     let resObj = this.state.resolveObjectResponse;
718     let resObjCount =
719       resObj.post || resObj.person || resObj.community || resObj.comment
720         ? 1
721         : 0;
722     return (
723       res.posts.length +
724       res.comments.length +
725       res.communities.length +
726       res.users.length +
727       resObjCount
728     );
729   }
730
731   handlePageChange(page: number) {
732     this.updateUrl({ page });
733   }
734
735   search() {
736     let form: SearchForm = {
737       q: this.state.q,
738       type_: this.state.type_,
739       sort: this.state.sort,
740       listing_type: this.state.listingType,
741       page: this.state.page,
742       limit: fetchLimit,
743       auth: authField(false),
744     };
745     if (this.state.communityId !== 0) {
746       form.community_id = this.state.communityId;
747     }
748     if (this.state.creatorId !== 0) {
749       form.creator_id = this.state.creatorId;
750     }
751
752     let resolveObjectForm: ResolveObject = {
753       q: this.state.q,
754       auth: authField(false),
755     };
756
757     if (this.state.q != "") {
758       WebSocketService.Instance.send(wsClient.search(form));
759       WebSocketService.Instance.send(wsClient.resolveObject(resolveObjectForm));
760     }
761   }
762
763   setupCommunityFilter() {
764     if (isBrowser()) {
765       let selectId: any = document.getElementById("community-filter");
766       if (selectId) {
767         this.communityChoices = new Choices(selectId, choicesConfig);
768         this.communityChoices.passedElement.element.addEventListener(
769           "choice",
770           (e: any) => {
771             this.handleCommunityFilterChange(Number(e.detail.choice.value));
772           },
773           false
774         );
775         this.communityChoices.passedElement.element.addEventListener(
776           "search",
777           debounce(async (e: any) => {
778             let communities = (await fetchCommunities(e.detail.value))
779               .communities;
780             let choices = communities.map(cv => communityToChoice(cv));
781             choices.unshift({ value: "0", label: i18n.t("all") });
782             this.communityChoices.setChoices(choices, "value", "label", true);
783           }, 400),
784           false
785         );
786       }
787     }
788   }
789
790   setupCreatorFilter() {
791     if (isBrowser()) {
792       let selectId: any = document.getElementById("creator-filter");
793       if (selectId) {
794         this.creatorChoices = new Choices(selectId, choicesConfig);
795         this.creatorChoices.passedElement.element.addEventListener(
796           "choice",
797           (e: any) => {
798             this.handleCreatorFilterChange(Number(e.detail.choice.value));
799           },
800           false
801         );
802         this.creatorChoices.passedElement.element.addEventListener(
803           "search",
804           debounce(async (e: any) => {
805             let creators = (await fetchUsers(e.detail.value)).users;
806             let choices = creators.map(pvs => personToChoice(pvs));
807             choices.unshift({ value: "0", label: i18n.t("all") });
808             this.creatorChoices.setChoices(choices, "value", "label", true);
809           }, 400),
810           false
811         );
812       }
813     }
814   }
815
816   handleSortChange(val: SortType) {
817     this.updateUrl({ sort: val, page: 1 });
818   }
819
820   handleTypeChange(i: Search, event: any) {
821     i.updateUrl({
822       type_: SearchType[event.target.value],
823       page: 1,
824     });
825   }
826
827   handleListingTypeChange(val: ListingType) {
828     this.updateUrl({
829       listingType: val,
830       page: 1,
831     });
832   }
833
834   handleCommunityFilterChange(communityId: number) {
835     this.updateUrl({
836       communityId,
837       page: 1,
838     });
839   }
840
841   handleCreatorFilterChange(creatorId: number) {
842     this.updateUrl({
843       creatorId,
844       page: 1,
845     });
846   }
847
848   handleSearchSubmit(i: Search, event: any) {
849     event.preventDefault();
850     i.updateUrl({
851       q: i.state.searchText,
852       type_: i.state.type_,
853       listingType: i.state.listingType,
854       communityId: i.state.communityId,
855       creatorId: i.state.creatorId,
856       sort: i.state.sort,
857       page: i.state.page,
858     });
859   }
860
861   handleQChange(i: Search, event: any) {
862     i.setState({ searchText: event.target.value });
863   }
864
865   updateUrl(paramUpdates: UrlParams) {
866     const qStr = paramUpdates.q || this.state.q;
867     const qStrEncoded = encodeURIComponent(qStr);
868     const typeStr = paramUpdates.type_ || this.state.type_;
869     const listingTypeStr = paramUpdates.listingType || this.state.listingType;
870     const sortStr = paramUpdates.sort || this.state.sort;
871     const communityId =
872       paramUpdates.communityId == 0
873         ? 0
874         : paramUpdates.communityId || this.state.communityId;
875     const creatorId =
876       paramUpdates.creatorId == 0
877         ? 0
878         : paramUpdates.creatorId || this.state.creatorId;
879     const page = paramUpdates.page || this.state.page;
880     this.props.history.push(
881       `/search/q/${qStrEncoded}/type/${typeStr}/sort/${sortStr}/listing_type/${listingTypeStr}/community_id/${communityId}/creator_id/${creatorId}/page/${page}`
882     );
883   }
884
885   parseMessage(msg: any) {
886     console.log(msg);
887     let op = wsUserOp(msg);
888     if (msg.error) {
889       if (msg.error != "couldnt_find_object") {
890         toast(i18n.t(msg.error), "danger");
891         return;
892       } else {
893         this.setState({
894           resolveObjectResponse: {
895             comment: null,
896             community: null,
897             person: null,
898             post: null,
899           },
900         });
901       }
902     } else if (op == UserOperation.Search) {
903       let data = wsJsonToRes<SearchResponse>(msg).data;
904       this.state.searchResponse = data;
905       this.state.loading = false;
906       window.scrollTo(0, 0);
907       this.setState(this.state);
908       restoreScrollPosition(this.context);
909     } else if (op == UserOperation.CreateCommentLike) {
910       let data = wsJsonToRes<CommentResponse>(msg).data;
911       createCommentLikeRes(
912         data.comment_view,
913         this.state.searchResponse.comments
914       );
915       this.setState(this.state);
916     } else if (op == UserOperation.CreatePostLike) {
917       let data = wsJsonToRes<PostResponse>(msg).data;
918       createPostLikeFindRes(data.post_view, this.state.searchResponse.posts);
919       this.setState(this.state);
920     } else if (op == UserOperation.ListCommunities) {
921       let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
922       this.state.communities = data.communities;
923       this.setState(this.state);
924       this.setupCommunityFilter();
925     } else if (op == UserOperation.ResolveObject) {
926       let data = wsJsonToRes<ResolveObjectResponse>(msg).data;
927       this.state.resolveObjectResponse = data;
928       this.setState(this.state);
929     }
930   }
931 }