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