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