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