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