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