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