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