]> Untitled Git - lemmy-ui.git/blob - src/shared/components/main.tsx
Running newer prettier.
[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   BanUserResponse,
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 { UserListing } from "./user-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.user) {
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.user) {
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.user
198       ? Object.values(ListingType)[
199           UserService.Instance.user.default_listing_type
200         ]
201       : ListingType.Local;
202     let sort: SortType = pathSplit[7]
203       ? SortType[pathSplit[7]]
204       : UserService.Instance.user
205       ? Object.values(SortType)[UserService.Instance.user.default_sort_type]
206       : SortType.Active;
207
208     let page = pathSplit[9] ? Number(pathSplit[9]) : 1;
209
210     let promises: Promise<any>[] = [];
211
212     if (dataType == DataType.Post) {
213       let getPostsForm: GetPosts = {
214         page,
215         limit: fetchLimit,
216         sort,
217         type_,
218       };
219       setOptionalAuth(getPostsForm, req.auth);
220       promises.push(req.client.getPosts(getPostsForm));
221     } else {
222       let getCommentsForm: GetComments = {
223         page,
224         limit: fetchLimit,
225         sort,
226         type_,
227       };
228       setOptionalAuth(getCommentsForm, req.auth);
229       promises.push(req.client.getComments(getCommentsForm));
230     }
231
232     let trendingCommunitiesForm: ListCommunities = {
233       type_: ListingType.Local,
234       sort: SortType.Hot,
235       limit: 6,
236     };
237     promises.push(req.client.listCommunities(trendingCommunitiesForm));
238
239     if (req.auth) {
240       promises.push(req.client.getFollowedCommunities({ auth: req.auth }));
241     }
242
243     return promises;
244   }
245
246   componentDidUpdate(_: any, lastState: MainState) {
247     if (
248       lastState.listingType !== this.state.listingType ||
249       lastState.dataType !== this.state.dataType ||
250       lastState.sort !== this.state.sort ||
251       lastState.page !== this.state.page
252     ) {
253       this.setState({ loading: true });
254       this.fetchData();
255     }
256   }
257
258   get documentTitle(): string {
259     return `${
260       this.state.siteRes.site_view
261         ? this.state.siteRes.site_view.site.name
262         : "Lemmy"
263     }`;
264   }
265
266   render() {
267     return (
268       <div class="container">
269         <HtmlTags
270           title={this.documentTitle}
271           path={this.context.router.route.match.url}
272         />
273         {this.state.siteRes.site_view?.site && (
274           <div class="row">
275             <main role="main" class="col-12 col-md-8">
276               {this.posts()}
277             </main>
278             <aside class="col-12 col-md-4">{this.mySidebar()}</aside>
279           </div>
280         )}
281       </div>
282     );
283   }
284
285   mySidebar() {
286     return (
287       <div>
288         {!this.state.loading && (
289           <div>
290             <div class="card border-secondary mb-3">
291               <div class="card-body">
292                 {this.trendingCommunities()}
293                 {this.createCommunityButton()}
294               </div>
295             </div>
296
297             {UserService.Instance.user &&
298               this.state.subscribedCommunities.length > 0 && (
299                 <div class="card border-secondary mb-3">
300                   <div class="card-body">{this.subscribedCommunities()}</div>
301                 </div>
302               )}
303
304             <div class="card border-secondary mb-3">
305               <div class="card-body">{this.sidebar()}</div>
306             </div>
307           </div>
308         )}
309       </div>
310     );
311   }
312
313   createCommunityButton() {
314     return (
315       <Link className="btn btn-secondary btn-block" to="/create_community">
316         {i18n.t("create_a_community")}
317       </Link>
318     );
319   }
320
321   trendingCommunities() {
322     return (
323       <div>
324         <h5>
325           <T i18nKey="trending_communities">
326             #
327             <Link className="text-body" to="/communities">
328               #
329             </Link>
330           </T>
331         </h5>
332         <ul class="list-inline">
333           {this.state.trendingCommunities.map(cv => (
334             <li class="list-inline-item d-inline-block">
335               <CommunityLink community={cv.community} />
336             </li>
337           ))}
338         </ul>
339       </div>
340     );
341   }
342
343   subscribedCommunities() {
344     return (
345       <div>
346         <h5>
347           <T i18nKey="subscribed_to_communities">
348             #
349             <Link className="text-body" to="/communities">
350               #
351             </Link>
352           </T>
353         </h5>
354         <ul class="list-inline mb-0">
355           {this.state.subscribedCommunities.map(cfv => (
356             <li class="list-inline-item d-inline-block">
357               <CommunityLink community={cfv.community} />
358             </li>
359           ))}
360         </ul>
361       </div>
362     );
363   }
364
365   sidebar() {
366     let site = this.state.siteRes.site_view.site;
367     return (
368       <div>
369         {!this.state.showEditSite ? (
370           <div>
371             <div class="mb-2">
372               {this.siteName()}
373               {this.adminButtons()}
374             </div>
375             <BannerIconHeader banner={site.banner} />
376             {this.siteInfo()}
377           </div>
378         ) : (
379           <SiteForm site={site} onCancel={this.handleEditCancel} />
380         )}
381       </div>
382     );
383   }
384
385   updateUrl(paramUpdates: UrlParams) {
386     const listingTypeStr = paramUpdates.listingType || this.state.listingType;
387     const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType];
388     const sortStr = paramUpdates.sort || this.state.sort;
389     const page = paramUpdates.page || this.state.page;
390     this.props.history.push(
391       `/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${page}`
392     );
393   }
394
395   siteInfo() {
396     return (
397       <div>
398         {this.state.siteRes.site_view.site.description &&
399           this.siteDescription()}
400         {this.badges()}
401         {this.admins()}
402       </div>
403     );
404   }
405
406   siteName() {
407     return <h5 class="mb-0">{`${this.documentTitle}`}</h5>;
408   }
409
410   admins() {
411     return (
412       <ul class="mt-1 list-inline small mb-0">
413         <li class="list-inline-item">{i18n.t("admins")}:</li>
414         {this.state.siteRes.admins.map(av => (
415           <li class="list-inline-item">
416             <UserListing user={av.user} />
417           </li>
418         ))}
419       </ul>
420     );
421   }
422
423   badges() {
424     let counts = this.state.siteRes.site_view.counts;
425     return (
426       <ul class="my-2 list-inline">
427         <li className="list-inline-item badge badge-secondary">
428           {i18n.t("number_online", { count: this.state.siteRes.online })}
429         </li>
430         <li
431           className="list-inline-item badge badge-secondary pointer"
432           data-tippy-content={`${i18n.t("number_of_users", {
433             count: counts.users_active_day,
434           })} ${i18n.t("active_in_the_last")} ${i18n.t("day")}`}
435         >
436           {i18n.t("number_of_users", {
437             count: counts.users_active_day,
438           })}{" "}
439           / {i18n.t("day")}
440         </li>
441         <li
442           className="list-inline-item badge badge-secondary pointer"
443           data-tippy-content={`${i18n.t("number_of_users", {
444             count: counts.users_active_week,
445           })} ${i18n.t("active_in_the_last")} ${i18n.t("week")}`}
446         >
447           {i18n.t("number_of_users", {
448             count: counts.users_active_week,
449           })}{" "}
450           / {i18n.t("week")}
451         </li>
452         <li
453           className="list-inline-item badge badge-secondary pointer"
454           data-tippy-content={`${i18n.t("number_of_users", {
455             count: counts.users_active_month,
456           })} ${i18n.t("active_in_the_last")} ${i18n.t("month")}`}
457         >
458           {i18n.t("number_of_users", {
459             count: counts.users_active_month,
460           })}{" "}
461           / {i18n.t("month")}
462         </li>
463         <li
464           className="list-inline-item badge badge-secondary pointer"
465           data-tippy-content={`${i18n.t("number_of_users", {
466             count: counts.users_active_half_year,
467           })} ${i18n.t("active_in_the_last")} ${i18n.t("number_of_months", {
468             count: 6,
469           })}`}
470         >
471           {i18n.t("number_of_users", {
472             count: counts.users_active_half_year,
473           })}{" "}
474           / {i18n.t("number_of_months", { count: 6 })}
475         </li>
476         <li className="list-inline-item badge badge-secondary">
477           {i18n.t("number_of_subscribers", {
478             count: counts.users,
479           })}
480         </li>
481         <li className="list-inline-item badge badge-secondary">
482           {i18n.t("number_of_communities", {
483             count: counts.communities,
484           })}
485         </li>
486         <li className="list-inline-item badge badge-secondary">
487           {i18n.t("number_of_posts", {
488             count: counts.posts,
489           })}
490         </li>
491         <li className="list-inline-item badge badge-secondary">
492           {i18n.t("number_of_comments", {
493             count: counts.comments,
494           })}
495         </li>
496         <li className="list-inline-item">
497           <Link className="badge badge-secondary" to="/modlog">
498             {i18n.t("modlog")}
499           </Link>
500         </li>
501       </ul>
502     );
503   }
504
505   adminButtons() {
506     return (
507       this.canAdmin && (
508         <ul class="list-inline mb-1 text-muted font-weight-bold">
509           <li className="list-inline-item-action">
510             <span
511               class="pointer"
512               role="button"
513               onClick={linkEvent(this, this.handleEditClick)}
514               aria-label={i18n.t("edit")}
515               data-tippy-content={i18n.t("edit")}
516             >
517               <Icon icon="edit" classes="icon-inline" />
518             </span>
519           </li>
520         </ul>
521       )
522     );
523   }
524
525   siteDescription() {
526     return (
527       <div
528         className="md-div"
529         dangerouslySetInnerHTML={mdToHtml(
530           this.state.siteRes.site_view.site.description
531         )}
532       />
533     );
534   }
535
536   posts() {
537     return (
538       <div class="main-content-wrapper">
539         {this.state.loading ? (
540           <h5>
541             <Spinner />
542           </h5>
543         ) : (
544           <div>
545             {this.selects()}
546             {this.listings()}
547             {this.paginator()}
548           </div>
549         )}
550       </div>
551     );
552   }
553
554   listings() {
555     let site = this.state.siteRes.site_view.site;
556     return this.state.dataType == DataType.Post ? (
557       <PostListings
558         posts={this.state.posts}
559         showCommunity
560         removeDuplicates
561         enableDownvotes={site.enable_downvotes}
562         enableNsfw={site.enable_nsfw}
563       />
564     ) : (
565       <CommentNodes
566         nodes={commentsToFlatNodes(this.state.comments)}
567         noIndent
568         showCommunity
569         showContext
570         enableDownvotes={site.enable_downvotes}
571       />
572     );
573   }
574
575   selects() {
576     return (
577       <div className="mb-3">
578         <span class="mr-3">
579           <DataTypeSelect
580             type_={this.state.dataType}
581             onChange={this.handleDataTypeChange}
582           />
583         </span>
584         <span class="mr-3">
585           <ListingTypeSelect
586             type_={this.state.listingType}
587             showLocal={this.showLocal}
588             onChange={this.handleListingTypeChange}
589           />
590         </span>
591         <span class="mr-2">
592           <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
593         </span>
594         {this.state.listingType == ListingType.All && (
595           <a
596             href={`/feeds/all.xml?sort=${this.state.sort}`}
597             rel="noopener"
598             title="RSS"
599           >
600             <Icon icon="rss" classes="text-muted small" />
601           </a>
602         )}
603         {this.state.listingType == ListingType.Local && (
604           <a
605             href={`/feeds/local.xml?sort=${this.state.sort}`}
606             rel="noopener"
607             title="RSS"
608           >
609             <Icon icon="rss" classes="text-muted small" />
610           </a>
611         )}
612         {UserService.Instance.user &&
613           this.state.listingType == ListingType.Subscribed && (
614             <a
615               href={`/feeds/front/${UserService.Instance.auth}.xml?sort=${this.state.sort}`}
616               title="RSS"
617               rel="noopener"
618             >
619               <Icon icon="rss" classes="text-muted small" />
620             </a>
621           )}
622       </div>
623     );
624   }
625
626   paginator() {
627     return (
628       <div class="my-2">
629         {this.state.page > 1 && (
630           <button
631             class="btn btn-secondary mr-1"
632             onClick={linkEvent(this, this.prevPage)}
633           >
634             {i18n.t("prev")}
635           </button>
636         )}
637         {this.state.posts.length > 0 && (
638           <button
639             class="btn btn-secondary"
640             onClick={linkEvent(this, this.nextPage)}
641           >
642             {i18n.t("next")}
643           </button>
644         )}
645       </div>
646     );
647   }
648
649   get showLocal(): boolean {
650     return this.isoData.site_res.federated_instances?.linked.length > 0;
651   }
652
653   get canAdmin(): boolean {
654     return (
655       UserService.Instance.user &&
656       this.state.siteRes.admins
657         .map(a => a.user.id)
658         .includes(UserService.Instance.user.id)
659     );
660   }
661
662   handleEditClick(i: Main) {
663     i.state.showEditSite = true;
664     i.setState(i.state);
665   }
666
667   handleEditCancel() {
668     this.state.showEditSite = false;
669     this.setState(this.state);
670   }
671
672   nextPage(i: Main) {
673     i.updateUrl({ page: i.state.page + 1 });
674     window.scrollTo(0, 0);
675   }
676
677   prevPage(i: Main) {
678     i.updateUrl({ page: i.state.page - 1 });
679     window.scrollTo(0, 0);
680   }
681
682   handleSortChange(val: SortType) {
683     this.updateUrl({ sort: val, page: 1 });
684     window.scrollTo(0, 0);
685   }
686
687   handleListingTypeChange(val: ListingType) {
688     this.updateUrl({ listingType: val, page: 1 });
689     window.scrollTo(0, 0);
690   }
691
692   handleDataTypeChange(val: DataType) {
693     this.updateUrl({ dataType: DataType[val], page: 1 });
694     window.scrollTo(0, 0);
695   }
696
697   fetchData() {
698     if (this.state.dataType == DataType.Post) {
699       let getPostsForm: GetPosts = {
700         page: this.state.page,
701         limit: fetchLimit,
702         sort: this.state.sort,
703         type_: this.state.listingType,
704         auth: authField(false),
705       };
706       WebSocketService.Instance.send(wsClient.getPosts(getPostsForm));
707     } else {
708       let getCommentsForm: GetComments = {
709         page: this.state.page,
710         limit: fetchLimit,
711         sort: this.state.sort,
712         type_: this.state.listingType,
713         auth: authField(false),
714       };
715       WebSocketService.Instance.send(wsClient.getComments(getCommentsForm));
716     }
717   }
718
719   parseMessage(msg: any) {
720     let op = wsUserOp(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.GetFollowedCommunities) {
730       let data = wsJsonToRes<GetFollowedCommunitiesResponse>(msg).data;
731       this.state.subscribedCommunities = data.communities;
732       this.setState(this.state);
733     } else if (op == UserOperation.ListCommunities) {
734       let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
735       this.state.trendingCommunities = data.communities;
736       this.setState(this.state);
737     } else if (op == UserOperation.EditSite) {
738       let data = wsJsonToRes<SiteResponse>(msg).data;
739       this.state.siteRes.site_view = data.site_view;
740       this.state.showEditSite = false;
741       this.setState(this.state);
742       toast(i18n.t("site_saved"));
743     } else if (op == UserOperation.GetPosts) {
744       let data = wsJsonToRes<GetPostsResponse>(msg).data;
745       this.state.posts = data.posts;
746       this.state.loading = false;
747       this.setState(this.state);
748       restoreScrollPosition(this.context);
749       setupTippy();
750     } else if (op == UserOperation.CreatePost) {
751       let data = wsJsonToRes<PostResponse>(msg).data;
752
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.user &&
759           UserService.Instance.user.show_nsfw);
760
761       // Only push these if you're on the first page, and you pass the nsfw check
762       if (this.state.page == 1 && nsfwCheck) {
763         // If you're on subscribed, only push it if you're subscribed.
764         if (this.state.listingType == ListingType.Subscribed) {
765           if (
766             this.state.subscribedCommunities
767               .map(c => c.community.id)
768               .includes(data.post_view.community.id)
769           ) {
770             this.state.posts.unshift(data.post_view);
771             notifyPost(data.post_view, this.context.router);
772           }
773         } else if (this.state.listingType == ListingType.Local) {
774           // If you're on the local view, only push it if its local
775           if (data.post_view.post.local) {
776             this.state.posts.unshift(data.post_view);
777             notifyPost(data.post_view, this.context.router);
778           }
779         } else {
780           this.state.posts.unshift(data.post_view);
781           notifyPost(data.post_view, this.context.router);
782         }
783         this.setState(this.state);
784       }
785     } else if (
786       op == UserOperation.EditPost ||
787       op == UserOperation.DeletePost ||
788       op == UserOperation.RemovePost ||
789       op == UserOperation.LockPost ||
790       op == UserOperation.StickyPost ||
791       op == UserOperation.SavePost
792     ) {
793       let data = wsJsonToRes<PostResponse>(msg).data;
794       editPostFindRes(data.post_view, this.state.posts);
795       this.setState(this.state);
796     } else if (op == UserOperation.CreatePostLike) {
797       let data = wsJsonToRes<PostResponse>(msg).data;
798       createPostLikeFindRes(data.post_view, this.state.posts);
799       this.setState(this.state);
800     } else if (op == UserOperation.AddAdmin) {
801       let data = wsJsonToRes<AddAdminResponse>(msg).data;
802       this.state.siteRes.admins = data.admins;
803       this.setState(this.state);
804     } else if (op == UserOperation.BanUser) {
805       let data = wsJsonToRes<BanUserResponse>(msg).data;
806       let found = this.state.siteRes.banned.find(
807         u => (u.user.id = data.user_view.user.id)
808       );
809
810       // Remove the banned if its found in the list, and the action is an unban
811       if (found && !data.banned) {
812         this.state.siteRes.banned = this.state.siteRes.banned.filter(
813           i => i.user.id !== data.user_view.user.id
814         );
815       } else {
816         this.state.siteRes.banned.push(data.user_view);
817       }
818
819       this.state.posts
820         .filter(p => p.creator.id == data.user_view.user.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).data;
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).data;
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).data;
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             this.state.subscribedCommunities
846               .map(c => c.community.id)
847               .includes(data.comment_view.community.id)
848           ) {
849             this.state.comments.unshift(data.comment_view);
850           }
851         } else {
852           this.state.comments.unshift(data.comment_view);
853         }
854         this.setState(this.state);
855       }
856     } else if (op == UserOperation.SaveComment) {
857       let data = wsJsonToRes<CommentResponse>(msg).data;
858       saveCommentRes(data.comment_view, this.state.comments);
859       this.setState(this.state);
860     } else if (op == UserOperation.CreateCommentLike) {
861       let data = wsJsonToRes<CommentResponse>(msg).data;
862       createCommentLikeRes(data.comment_view, this.state.comments);
863       this.setState(this.state);
864     }
865   }
866 }