]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/home.tsx
Adding sidebar and subscribed collapse. Fixes #609 (#622)
[lemmy-ui.git] / src / shared / components / home / home.tsx
1 import { Component, linkEvent } from "inferno";
2 import { T } from "inferno-i18next-dess";
3 import { Link } from "inferno-router";
4 import {
5   AddAdminResponse,
6   BanPersonResponse,
7   BlockPersonResponse,
8   CommentReportResponse,
9   CommentResponse,
10   CommentView,
11   CommunityView,
12   GetComments,
13   GetCommentsResponse,
14   GetPosts,
15   GetPostsResponse,
16   GetSiteResponse,
17   ListCommunities,
18   ListCommunitiesResponse,
19   ListingType,
20   PostReportResponse,
21   PostResponse,
22   PostView,
23   SiteResponse,
24   SortType,
25   UserOperation,
26 } from "lemmy-js-client";
27 import { Subscription } from "rxjs";
28 import { i18n } from "../../i18next";
29 import { DataType, InitialFetchRequest } from "../../interfaces";
30 import { UserService, WebSocketService } from "../../services";
31 import {
32   authField,
33   commentsToFlatNodes,
34   createCommentLikeRes,
35   createPostLikeFindRes,
36   editCommentRes,
37   editPostFindRes,
38   fetchLimit,
39   getDataTypeFromProps,
40   getListingTypeFromProps,
41   getPageFromProps,
42   getSortTypeFromProps,
43   mdToHtml,
44   notifyPost,
45   numToSI,
46   relTags,
47   restoreScrollPosition,
48   saveCommentRes,
49   saveScrollPosition,
50   setIsoData,
51   setOptionalAuth,
52   setupTippy,
53   showLocal,
54   toast,
55   updatePersonBlock,
56   wsClient,
57   wsJsonToRes,
58   wsSubscribe,
59   wsUserOp,
60 } from "../../utils";
61 import { CommentNodes } from "../comment/comment-nodes";
62 import { BannerIconHeader } from "../common/banner-icon-header";
63 import { DataTypeSelect } from "../common/data-type-select";
64 import { HtmlTags } from "../common/html-tags";
65 import { Icon, Spinner } from "../common/icon";
66 import { ListingTypeSelect } from "../common/listing-type-select";
67 import { Paginator } from "../common/paginator";
68 import { SortSelect } from "../common/sort-select";
69 import { CommunityLink } from "../community/community-link";
70 import { PersonListing } from "../person/person-listing";
71 import { PostListings } from "../post/post-listings";
72 import { SiteForm } from "./site-form";
73
74 interface HomeState {
75   trendingCommunities: CommunityView[];
76   siteRes: GetSiteResponse;
77   showEditSite: boolean;
78   showSubscribedMobile: boolean;
79   showTrendingMobile: boolean;
80   showSidebarMobile: boolean;
81   sidebarCollapsed: boolean;
82   subscribedCollapsed: boolean;
83   loading: boolean;
84   posts: PostView[];
85   comments: CommentView[];
86   listingType: ListingType;
87   dataType: DataType;
88   sort: SortType;
89   page: number;
90 }
91
92 interface HomeProps {
93   listingType: ListingType;
94   dataType: DataType;
95   sort: SortType;
96   page: number;
97 }
98
99 interface UrlParams {
100   listingType?: ListingType;
101   dataType?: string;
102   sort?: SortType;
103   page?: number;
104 }
105
106 export class Home extends Component<any, HomeState> {
107   private isoData = setIsoData(this.context);
108   private subscription: Subscription;
109   private emptyState: HomeState = {
110     trendingCommunities: [],
111     siteRes: this.isoData.site_res,
112     showEditSite: false,
113     showSubscribedMobile: false,
114     showTrendingMobile: false,
115     showSidebarMobile: false,
116     subscribedCollapsed: false,
117     sidebarCollapsed: false,
118     loading: true,
119     posts: [],
120     comments: [],
121     listingType: getListingTypeFromProps(this.props),
122     dataType: getDataTypeFromProps(this.props),
123     sort: getSortTypeFromProps(this.props),
124     page: getPageFromProps(this.props),
125   };
126
127   constructor(props: any, context: any) {
128     super(props, context);
129
130     this.state = this.emptyState;
131     this.handleEditCancel = this.handleEditCancel.bind(this);
132     this.handleSortChange = this.handleSortChange.bind(this);
133     this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
134     this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
135     this.handlePageChange = this.handlePageChange.bind(this);
136
137     this.parseMessage = this.parseMessage.bind(this);
138     this.subscription = wsSubscribe(this.parseMessage);
139
140     // Only fetch the data if coming from another route
141     if (this.isoData.path == this.context.router.route.match.url) {
142       if (this.state.dataType == DataType.Post) {
143         this.state.posts = this.isoData.routeData[0].posts;
144       } else {
145         this.state.comments = this.isoData.routeData[0].comments;
146       }
147       this.state.trendingCommunities = this.isoData.routeData[1].communities;
148       this.state.loading = false;
149     } else {
150       this.fetchTrendingCommunities();
151       this.fetchData();
152     }
153   }
154
155   fetchTrendingCommunities() {
156     let listCommunitiesForm: ListCommunities = {
157       type_: ListingType.Local,
158       sort: SortType.Hot,
159       limit: 6,
160       auth: authField(false),
161     };
162     WebSocketService.Instance.send(
163       wsClient.listCommunities(listCommunitiesForm)
164     );
165   }
166
167   componentDidMount() {
168     // This means it hasn't been set up yet
169     if (!this.state.siteRes.site_view) {
170       this.context.router.history.push("/setup");
171     }
172
173     WebSocketService.Instance.send(wsClient.communityJoin({ community_id: 0 }));
174     setupTippy();
175   }
176
177   componentWillUnmount() {
178     saveScrollPosition(this.context);
179     this.subscription.unsubscribe();
180     window.isoData.path = undefined;
181   }
182
183   static getDerivedStateFromProps(props: any): HomeProps {
184     return {
185       listingType: getListingTypeFromProps(props),
186       dataType: getDataTypeFromProps(props),
187       sort: getSortTypeFromProps(props),
188       page: getPageFromProps(props),
189     };
190   }
191
192   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
193     let pathSplit = req.path.split("/");
194     let dataType: DataType = pathSplit[3]
195       ? DataType[pathSplit[3]]
196       : DataType.Post;
197
198     // TODO figure out auth default_listingType, default_sort_type
199     let type_: ListingType = pathSplit[5]
200       ? ListingType[pathSplit[5]]
201       : UserService.Instance.myUserInfo
202       ? Object.values(ListingType)[
203           UserService.Instance.myUserInfo.local_user_view.local_user
204             .default_listing_type
205         ]
206       : ListingType.Local;
207     let sort: SortType = pathSplit[7]
208       ? SortType[pathSplit[7]]
209       : UserService.Instance.myUserInfo
210       ? Object.values(SortType)[
211           UserService.Instance.myUserInfo.local_user_view.local_user
212             .default_sort_type
213         ]
214       : SortType.Active;
215
216     let page = pathSplit[9] ? Number(pathSplit[9]) : 1;
217
218     let promises: Promise<any>[] = [];
219
220     if (dataType == DataType.Post) {
221       let getPostsForm: GetPosts = {
222         page,
223         limit: fetchLimit,
224         sort,
225         type_,
226         saved_only: false,
227       };
228       setOptionalAuth(getPostsForm, req.auth);
229       promises.push(req.client.getPosts(getPostsForm));
230     } else {
231       let getCommentsForm: GetComments = {
232         page,
233         limit: fetchLimit,
234         sort,
235         type_,
236         saved_only: false,
237       };
238       setOptionalAuth(getCommentsForm, req.auth);
239       promises.push(req.client.getComments(getCommentsForm));
240     }
241
242     let trendingCommunitiesForm: ListCommunities = {
243       type_: ListingType.Local,
244       sort: SortType.Hot,
245       limit: 6,
246     };
247     setOptionalAuth(trendingCommunitiesForm, req.auth);
248     promises.push(req.client.listCommunities(trendingCommunitiesForm));
249
250     return promises;
251   }
252
253   componentDidUpdate(_: any, lastState: HomeState) {
254     if (
255       lastState.listingType !== this.state.listingType ||
256       lastState.dataType !== this.state.dataType ||
257       lastState.sort !== this.state.sort ||
258       lastState.page !== this.state.page
259     ) {
260       this.setState({ loading: true });
261       this.fetchData();
262     }
263   }
264
265   get documentTitle(): string {
266     return `${
267       this.state.siteRes.site_view
268         ? this.state.siteRes.site_view.site.description
269           ? `${this.state.siteRes.site_view.site.name} - ${this.state.siteRes.site_view.site.description}`
270           : this.state.siteRes.site_view.site.name
271         : "Lemmy"
272     }`;
273   }
274
275   render() {
276     return (
277       <div class="container">
278         <HtmlTags
279           title={this.documentTitle}
280           path={this.context.router.route.match.url}
281         />
282         {this.state.siteRes.site_view?.site && (
283           <div class="row">
284             <main role="main" class="col-12 col-md-8">
285               <div class="d-block d-md-none">{this.mobileView()}</div>
286               {this.posts()}
287             </main>
288             <aside class="d-none d-md-block col-md-4">{this.mySidebar()}</aside>
289           </div>
290         )}
291       </div>
292     );
293   }
294
295   mobileView() {
296     return (
297       <div class="row">
298         <div class="col-12">
299           {UserService.Instance.myUserInfo &&
300             UserService.Instance.myUserInfo.follows.length > 0 && (
301               <button
302                 class="btn btn-secondary d-inline-block mb-2 mr-3"
303                 onClick={linkEvent(this, this.handleShowSubscribedMobile)}
304               >
305                 {i18n.t("subscribed")}{" "}
306                 <Icon
307                   icon={
308                     this.state.showSubscribedMobile
309                       ? `minus-square`
310                       : `plus-square`
311                   }
312                   classes="icon-inline"
313                 />
314               </button>
315             )}
316           <button
317             class="btn btn-secondary d-inline-block mb-2 mr-3"
318             onClick={linkEvent(this, this.handleShowTrendingMobile)}
319           >
320             {i18n.t("trending")}{" "}
321             <Icon
322               icon={
323                 this.state.showTrendingMobile ? `minus-square` : `plus-square`
324               }
325               classes="icon-inline"
326             />
327           </button>
328           <button
329             class="btn btn-secondary d-inline-block mb-2 mr-3"
330             onClick={linkEvent(this, this.handleShowSidebarMobile)}
331           >
332             {i18n.t("sidebar")}{" "}
333             <Icon
334               icon={
335                 this.state.showSidebarMobile ? `minus-square` : `plus-square`
336               }
337               classes="icon-inline"
338             />
339           </button>
340           {this.state.showSubscribedMobile && (
341             <div class="col-12 card border-secondary mb-3">
342               <div class="card-body">{this.subscribedCommunities()}</div>
343             </div>
344           )}
345           {this.state.showTrendingMobile && (
346             <div class="col-12 card border-secondary mb-3">
347               <div class="card-body">{this.trendingCommunities()}</div>
348             </div>
349           )}
350           {this.state.showSidebarMobile && (
351             <div class="col-12 card border-secondary mb-3">
352               <div class="card-body">{this.sidebar()}</div>
353             </div>
354           )}
355         </div>
356       </div>
357     );
358   }
359
360   mySidebar() {
361     return (
362       <div>
363         {!this.state.loading && (
364           <div>
365             <div class="card border-secondary mb-3">
366               <div class="card-body">
367                 {this.trendingCommunities()}
368                 {this.createCommunityButton()}
369                 {this.exploreCommunitiesButton()}
370               </div>
371             </div>
372
373             <div class="card border-secondary mb-3">
374               <div class="card-body">{this.sidebar()}</div>
375             </div>
376
377             {UserService.Instance.myUserInfo &&
378               UserService.Instance.myUserInfo.follows.length > 0 && (
379                 <div class="card border-secondary mb-3">
380                   <div class="card-body">{this.subscribedCommunities()}</div>
381                 </div>
382               )}
383           </div>
384         )}
385       </div>
386     );
387   }
388
389   createCommunityButton() {
390     return (
391       <Link className="mt-2 btn btn-secondary btn-block" to="/create_community">
392         {i18n.t("create_a_community")}
393       </Link>
394     );
395   }
396
397   exploreCommunitiesButton() {
398     return (
399       <Link className="btn btn-secondary btn-block" to="/communities">
400         {i18n.t("explore_communities")}
401       </Link>
402     );
403   }
404
405   trendingCommunities() {
406     return (
407       <div>
408         <h5>
409           <T i18nKey="trending_communities">
410             #
411             <Link className="text-body" to="/communities">
412               #
413             </Link>
414           </T>
415         </h5>
416         <ul class="list-inline mb-0">
417           {this.state.trendingCommunities.map(cv => (
418             <li class="list-inline-item d-inline-block">
419               <CommunityLink community={cv.community} />
420             </li>
421           ))}
422         </ul>
423       </div>
424     );
425   }
426
427   subscribedCommunities() {
428     return (
429       <div>
430         <h5>
431           <T class="d-inline" i18nKey="subscribed_to_communities">
432             #
433             <Link className="text-body" to="/communities">
434               #
435             </Link>
436           </T>
437           <button
438             class="btn btn-sm text-muted"
439             onClick={linkEvent(this, this.handleCollapseSubscribe)}
440             aria-label={i18n.t("collapse")}
441             data-tippy-content={i18n.t("collapse")}
442           >
443             {this.state.subscribedCollapsed ? (
444               <Icon icon="plus-square" classes="icon-inline" />
445             ) : (
446               <Icon icon="minus-square" classes="icon-inline" />
447             )}
448           </button>
449         </h5>
450         {!this.state.subscribedCollapsed && (
451           <ul class="list-inline mb-0">
452             {UserService.Instance.myUserInfo.follows.map(cfv => (
453               <li class="list-inline-item d-inline-block">
454                 <CommunityLink community={cfv.community} />
455               </li>
456             ))}
457           </ul>
458         )}
459       </div>
460     );
461   }
462
463   sidebar() {
464     let site = this.state.siteRes.site_view.site;
465     return (
466       <div>
467         {!this.state.showEditSite ? (
468           <div>
469             <div class="mb-2">
470               {this.siteName()}
471               {this.adminButtons()}
472             </div>
473             {!this.state.sidebarCollapsed && (
474               <>
475                 <BannerIconHeader banner={site.banner} />
476                 {this.siteInfo()}
477               </>
478             )}
479           </div>
480         ) : (
481           <SiteForm site={site} onCancel={this.handleEditCancel} />
482         )}
483       </div>
484     );
485   }
486
487   updateUrl(paramUpdates: UrlParams) {
488     const listingTypeStr = paramUpdates.listingType || this.state.listingType;
489     const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType];
490     const sortStr = paramUpdates.sort || this.state.sort;
491     const page = paramUpdates.page || this.state.page;
492     this.props.history.push(
493       `/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${page}`
494     );
495   }
496
497   siteInfo() {
498     let site = this.state.siteRes.site_view.site;
499     return (
500       <div>
501         {site.description && <h6>{site.description}</h6>}
502         {site.sidebar && this.siteSidebar()}
503         {this.badges()}
504         {this.admins()}
505       </div>
506     );
507   }
508
509   siteName() {
510     let site = this.state.siteRes.site_view.site;
511     return (
512       site.name && (
513         <h5 class="mb-0 d-inline">
514           {site.name}
515           <button
516             class="btn btn-sm text-muted"
517             onClick={linkEvent(this, this.handleCollapseSidebar)}
518             aria-label={i18n.t("collapse")}
519             data-tippy-content={i18n.t("collapse")}
520           >
521             {this.state.sidebarCollapsed ? (
522               <Icon icon="plus-square" classes="icon-inline" />
523             ) : (
524               <Icon icon="minus-square" classes="icon-inline" />
525             )}
526           </button>
527         </h5>
528       )
529     );
530   }
531
532   admins() {
533     return (
534       <ul class="mt-1 list-inline small mb-0">
535         <li class="list-inline-item">{i18n.t("admins")}:</li>
536         {this.state.siteRes.admins.map(av => (
537           <li class="list-inline-item">
538             <PersonListing person={av.person} />
539           </li>
540         ))}
541       </ul>
542     );
543   }
544
545   badges() {
546     let counts = this.state.siteRes.site_view.counts;
547     return (
548       <ul class="my-2 list-inline">
549         <li className="list-inline-item badge badge-secondary">
550           {i18n.t("number_online", {
551             count: this.state.siteRes.online,
552             formattedCount: numToSI(this.state.siteRes.online),
553           })}
554         </li>
555         <li
556           className="list-inline-item badge badge-secondary pointer"
557           data-tippy-content={i18n.t("active_users_in_the_last_day", {
558             count: counts.users_active_day,
559             formattedCount: numToSI(counts.users_active_day),
560           })}
561         >
562           {i18n.t("number_of_users", {
563             count: counts.users_active_day,
564             formattedCount: numToSI(counts.users_active_day),
565           })}{" "}
566           / {i18n.t("day")}
567         </li>
568         <li
569           className="list-inline-item badge badge-secondary pointer"
570           data-tippy-content={i18n.t("active_users_in_the_last_week", {
571             count: counts.users_active_week,
572             formattedCount: counts.users_active_week,
573           })}
574         >
575           {i18n.t("number_of_users", {
576             count: counts.users_active_week,
577             formattedCount: numToSI(counts.users_active_week),
578           })}{" "}
579           / {i18n.t("week")}
580         </li>
581         <li
582           className="list-inline-item badge badge-secondary pointer"
583           data-tippy-content={i18n.t("active_users_in_the_last_month", {
584             count: counts.users_active_month,
585             formattedCount: counts.users_active_month,
586           })}
587         >
588           {i18n.t("number_of_users", {
589             count: counts.users_active_month,
590             formattedCount: numToSI(counts.users_active_month),
591           })}{" "}
592           / {i18n.t("month")}
593         </li>
594         <li
595           className="list-inline-item badge badge-secondary pointer"
596           data-tippy-content={i18n.t("active_users_in_the_last_six_months", {
597             count: counts.users_active_half_year,
598             formattedCount: counts.users_active_half_year,
599           })}
600         >
601           {i18n.t("number_of_users", {
602             count: counts.users_active_half_year,
603             formattedCount: numToSI(counts.users_active_half_year),
604           })}{" "}
605           / {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
606         </li>
607         <li className="list-inline-item badge badge-secondary">
608           {i18n.t("number_of_users", {
609             count: counts.users,
610             formattedCount: numToSI(counts.users),
611           })}
612         </li>
613         <li className="list-inline-item badge badge-secondary">
614           {i18n.t("number_of_communities", {
615             count: counts.communities,
616             formattedCount: numToSI(counts.communities),
617           })}
618         </li>
619         <li className="list-inline-item badge badge-secondary">
620           {i18n.t("number_of_posts", {
621             count: counts.posts,
622             formattedCount: numToSI(counts.posts),
623           })}
624         </li>
625         <li className="list-inline-item badge badge-secondary">
626           {i18n.t("number_of_comments", {
627             count: counts.comments,
628             formattedCount: numToSI(counts.comments),
629           })}
630         </li>
631         <li className="list-inline-item">
632           <Link className="badge badge-primary" to="/modlog">
633             {i18n.t("modlog")}
634           </Link>
635         </li>
636       </ul>
637     );
638   }
639
640   adminButtons() {
641     return (
642       this.canAdmin && (
643         <ul class="list-inline mb-1 text-muted font-weight-bold">
644           <li className="list-inline-item-action">
645             <button
646               class="btn btn-link d-inline-block text-muted"
647               onClick={linkEvent(this, this.handleEditClick)}
648               aria-label={i18n.t("edit")}
649               data-tippy-content={i18n.t("edit")}
650             >
651               <Icon icon="edit" classes="icon-inline" />
652             </button>
653           </li>
654         </ul>
655       )
656     );
657   }
658
659   siteSidebar() {
660     return (
661       <div
662         className="md-div"
663         dangerouslySetInnerHTML={mdToHtml(
664           this.state.siteRes.site_view.site.sidebar
665         )}
666       />
667     );
668   }
669
670   posts() {
671     return (
672       <div class="main-content-wrapper">
673         {this.state.loading ? (
674           <h5>
675             <Spinner large />
676           </h5>
677         ) : (
678           <div>
679             {this.selects()}
680             {this.listings()}
681             <Paginator
682               page={this.state.page}
683               onChange={this.handlePageChange}
684             />
685           </div>
686         )}
687       </div>
688     );
689   }
690
691   listings() {
692     let site = this.state.siteRes.site_view.site;
693     return this.state.dataType == DataType.Post ? (
694       <PostListings
695         posts={this.state.posts}
696         showCommunity
697         removeDuplicates
698         enableDownvotes={site.enable_downvotes}
699         enableNsfw={site.enable_nsfw}
700       />
701     ) : (
702       <CommentNodes
703         nodes={commentsToFlatNodes(this.state.comments)}
704         noIndent
705         showCommunity
706         showContext
707         enableDownvotes={site.enable_downvotes}
708       />
709     );
710   }
711
712   selects() {
713     let allRss = `/feeds/all.xml?sort=${this.state.sort}`;
714     let localRss = `/feeds/local.xml?sort=${this.state.sort}`;
715     let frontRss = UserService.Instance.myUserInfo
716       ? `/feeds/front/${UserService.Instance.auth}.xml?sort=${this.state.sort}`
717       : "";
718
719     return (
720       <div className="mb-3">
721         <span class="mr-3">
722           <DataTypeSelect
723             type_={this.state.dataType}
724             onChange={this.handleDataTypeChange}
725           />
726         </span>
727         <span class="mr-3">
728           <ListingTypeSelect
729             type_={this.state.listingType}
730             showLocal={showLocal(this.isoData)}
731             onChange={this.handleListingTypeChange}
732           />
733         </span>
734         <span class="mr-2">
735           <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
736         </span>
737         {this.state.listingType == ListingType.All && (
738           <>
739             <a href={allRss} rel={relTags} title="RSS">
740               <Icon icon="rss" classes="text-muted small" />
741             </a>
742             <link rel="alternate" type="application/atom+xml" href={allRss} />
743           </>
744         )}
745         {this.state.listingType == ListingType.Local && (
746           <>
747             <a href={localRss} rel={relTags} title="RSS">
748               <Icon icon="rss" classes="text-muted small" />
749             </a>
750             <link rel="alternate" type="application/atom+xml" href={localRss} />
751           </>
752         )}
753         {UserService.Instance.myUserInfo &&
754           this.state.listingType == ListingType.Subscribed && (
755             <>
756               <a href={frontRss} title="RSS" rel={relTags}>
757                 <Icon icon="rss" classes="text-muted small" />
758               </a>
759               <link
760                 rel="alternate"
761                 type="application/atom+xml"
762                 href={frontRss}
763               />
764             </>
765           )}
766       </div>
767     );
768   }
769
770   get canAdmin(): boolean {
771     return (
772       UserService.Instance.myUserInfo &&
773       this.state.siteRes.admins
774         .map(a => a.person.id)
775         .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
776     );
777   }
778
779   handleEditClick(i: Home) {
780     i.state.showEditSite = true;
781     i.setState(i.state);
782   }
783
784   handleEditCancel() {
785     this.state.showEditSite = false;
786     this.setState(this.state);
787   }
788
789   handleShowSubscribedMobile(i: Home) {
790     i.state.showSubscribedMobile = !i.state.showSubscribedMobile;
791     i.setState(i.state);
792   }
793
794   handleShowTrendingMobile(i: Home) {
795     i.state.showTrendingMobile = !i.state.showTrendingMobile;
796     i.setState(i.state);
797   }
798
799   handleShowSidebarMobile(i: Home) {
800     i.state.showSidebarMobile = !i.state.showSidebarMobile;
801     i.setState(i.state);
802   }
803
804   handleCollapseSubscribe(i: Home) {
805     i.state.subscribedCollapsed = !i.state.subscribedCollapsed;
806     i.setState(i.state);
807   }
808
809   handleCollapseSidebar(i: Home) {
810     i.state.sidebarCollapsed = !i.state.sidebarCollapsed;
811     i.setState(i.state);
812   }
813
814   handlePageChange(page: number) {
815     this.updateUrl({ page });
816     window.scrollTo(0, 0);
817   }
818
819   handleSortChange(val: SortType) {
820     this.updateUrl({ sort: val, page: 1 });
821     window.scrollTo(0, 0);
822   }
823
824   handleListingTypeChange(val: ListingType) {
825     this.updateUrl({ listingType: val, page: 1 });
826     window.scrollTo(0, 0);
827   }
828
829   handleDataTypeChange(val: DataType) {
830     this.updateUrl({ dataType: DataType[val], page: 1 });
831     window.scrollTo(0, 0);
832   }
833
834   fetchData() {
835     if (this.state.dataType == DataType.Post) {
836       let getPostsForm: GetPosts = {
837         page: this.state.page,
838         limit: fetchLimit,
839         sort: this.state.sort,
840         type_: this.state.listingType,
841         saved_only: false,
842         auth: authField(false),
843       };
844       WebSocketService.Instance.send(wsClient.getPosts(getPostsForm));
845     } else {
846       let getCommentsForm: GetComments = {
847         page: this.state.page,
848         limit: fetchLimit,
849         sort: this.state.sort,
850         type_: this.state.listingType,
851         saved_only: false,
852         auth: authField(false),
853       };
854       WebSocketService.Instance.send(wsClient.getComments(getCommentsForm));
855     }
856   }
857
858   parseMessage(msg: any) {
859     let op = wsUserOp(msg);
860     console.log(msg);
861     if (msg.error) {
862       toast(i18n.t(msg.error), "danger");
863       return;
864     } else if (msg.reconnect) {
865       WebSocketService.Instance.send(
866         wsClient.communityJoin({ community_id: 0 })
867       );
868       this.fetchData();
869     } else if (op == UserOperation.ListCommunities) {
870       let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
871       this.state.trendingCommunities = data.communities;
872       this.setState(this.state);
873     } else if (op == UserOperation.EditSite) {
874       let data = wsJsonToRes<SiteResponse>(msg).data;
875       this.state.siteRes.site_view = data.site_view;
876       this.state.showEditSite = false;
877       this.setState(this.state);
878       toast(i18n.t("site_saved"));
879     } else if (op == UserOperation.GetPosts) {
880       let data = wsJsonToRes<GetPostsResponse>(msg).data;
881       this.state.posts = data.posts;
882       this.state.loading = false;
883       this.setState(this.state);
884       restoreScrollPosition(this.context);
885       setupTippy();
886     } else if (op == UserOperation.CreatePost) {
887       let data = wsJsonToRes<PostResponse>(msg).data;
888
889       // NSFW check
890       let nsfw = data.post_view.post.nsfw || data.post_view.community.nsfw;
891       let nsfwCheck =
892         !nsfw ||
893         (nsfw &&
894           UserService.Instance.myUserInfo &&
895           UserService.Instance.myUserInfo.local_user_view.local_user.show_nsfw);
896
897       // Only push these if you're on the first page, and you pass the nsfw check
898       if (this.state.page == 1 && nsfwCheck) {
899         // If you're on subscribed, only push it if you're subscribed.
900         if (this.state.listingType == ListingType.Subscribed) {
901           if (
902             UserService.Instance.myUserInfo.follows
903               .map(c => c.community.id)
904               .includes(data.post_view.community.id)
905           ) {
906             this.state.posts.unshift(data.post_view);
907             if (
908               UserService.Instance.myUserInfo?.local_user_view.local_user
909                 .show_new_post_notifs
910             ) {
911               notifyPost(data.post_view, this.context.router);
912             }
913           }
914         } else if (this.state.listingType == ListingType.Local) {
915           // If you're on the local view, only push it if its local
916           if (data.post_view.post.local) {
917             this.state.posts.unshift(data.post_view);
918             if (
919               UserService.Instance.myUserInfo?.local_user_view.local_user
920                 .show_new_post_notifs
921             ) {
922               notifyPost(data.post_view, this.context.router);
923             }
924           }
925         } else {
926           this.state.posts.unshift(data.post_view);
927           if (
928             UserService.Instance.myUserInfo?.local_user_view.local_user
929               .show_new_post_notifs
930           ) {
931             notifyPost(data.post_view, this.context.router);
932           }
933         }
934         this.setState(this.state);
935       }
936     } else if (
937       op == UserOperation.EditPost ||
938       op == UserOperation.DeletePost ||
939       op == UserOperation.RemovePost ||
940       op == UserOperation.LockPost ||
941       op == UserOperation.StickyPost ||
942       op == UserOperation.SavePost
943     ) {
944       let data = wsJsonToRes<PostResponse>(msg).data;
945       editPostFindRes(data.post_view, this.state.posts);
946       this.setState(this.state);
947     } else if (op == UserOperation.CreatePostLike) {
948       let data = wsJsonToRes<PostResponse>(msg).data;
949       createPostLikeFindRes(data.post_view, this.state.posts);
950       this.setState(this.state);
951     } else if (op == UserOperation.AddAdmin) {
952       let data = wsJsonToRes<AddAdminResponse>(msg).data;
953       this.state.siteRes.admins = data.admins;
954       this.setState(this.state);
955     } else if (op == UserOperation.BanPerson) {
956       let data = wsJsonToRes<BanPersonResponse>(msg).data;
957       this.state.posts
958         .filter(p => p.creator.id == data.person_view.person.id)
959         .forEach(p => (p.creator.banned = data.banned));
960
961       this.setState(this.state);
962     } else if (op == UserOperation.GetComments) {
963       let data = wsJsonToRes<GetCommentsResponse>(msg).data;
964       this.state.comments = data.comments;
965       this.state.loading = false;
966       this.setState(this.state);
967     } else if (
968       op == UserOperation.EditComment ||
969       op == UserOperation.DeleteComment ||
970       op == UserOperation.RemoveComment
971     ) {
972       let data = wsJsonToRes<CommentResponse>(msg).data;
973       editCommentRes(data.comment_view, this.state.comments);
974       this.setState(this.state);
975     } else if (op == UserOperation.CreateComment) {
976       let data = wsJsonToRes<CommentResponse>(msg).data;
977
978       // Necessary since it might be a user reply
979       if (data.form_id) {
980         // If you're on subscribed, only push it if you're subscribed.
981         if (this.state.listingType == ListingType.Subscribed) {
982           if (
983             UserService.Instance.myUserInfo.follows
984               .map(c => c.community.id)
985               .includes(data.comment_view.community.id)
986           ) {
987             this.state.comments.unshift(data.comment_view);
988           }
989         } else {
990           this.state.comments.unshift(data.comment_view);
991         }
992         this.setState(this.state);
993       }
994     } else if (op == UserOperation.SaveComment) {
995       let data = wsJsonToRes<CommentResponse>(msg).data;
996       saveCommentRes(data.comment_view, this.state.comments);
997       this.setState(this.state);
998     } else if (op == UserOperation.CreateCommentLike) {
999       let data = wsJsonToRes<CommentResponse>(msg).data;
1000       createCommentLikeRes(data.comment_view, this.state.comments);
1001       this.setState(this.state);
1002     } else if (op == UserOperation.BlockPerson) {
1003       let data = wsJsonToRes<BlockPersonResponse>(msg).data;
1004       updatePersonBlock(data);
1005     } else if (op == UserOperation.CreatePostReport) {
1006       let data = wsJsonToRes<PostReportResponse>(msg).data;
1007       if (data) {
1008         toast(i18n.t("report_created"));
1009       }
1010     } else if (op == UserOperation.CreateCommentReport) {
1011       let data = wsJsonToRes<CommentReportResponse>(msg).data;
1012       if (data) {
1013         toast(i18n.t("report_created"));
1014       }
1015     }
1016   }
1017 }