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