]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/home.tsx
Add Taglines support (#854)
[lemmy-ui.git] / src / shared / components / home / home.tsx
1 import { None, Option, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import { T } from "inferno-i18next-dess";
4 import { Link } from "inferno-router";
5 import {
6   AddAdminResponse,
7   BanPersonResponse,
8   BlockPersonResponse,
9   CommentReportResponse,
10   CommentResponse,
11   CommentView,
12   CommunityView,
13   GetComments,
14   GetCommentsResponse,
15   GetPosts,
16   GetPostsResponse,
17   GetSiteResponse,
18   ListCommunities,
19   ListCommunitiesResponse,
20   ListingType,
21   PostReportResponse,
22   PostResponse,
23   PostView,
24   PurgeItemResponse,
25   SiteResponse,
26   SortType,
27   UserOperation,
28   wsJsonToRes,
29   wsUserOp,
30 } from "lemmy-js-client";
31 import { Subscription } from "rxjs";
32 import { i18n } from "../../i18next";
33 import {
34   CommentViewType,
35   DataType,
36   InitialFetchRequest,
37 } from "../../interfaces";
38 import { UserService, WebSocketService } from "../../services";
39 import {
40   auth,
41   canCreateCommunity,
42   commentsToFlatNodes,
43   createCommentLikeRes,
44   createPostLikeFindRes,
45   editCommentRes,
46   editPostFindRes,
47   enableDownvotes,
48   enableNsfw,
49   fetchLimit,
50   getDataTypeFromProps,
51   getListingTypeFromProps,
52   getPageFromProps,
53   getRandomFromList,
54   getSortTypeFromProps,
55   isBrowser,
56   isPostBlocked,
57   mdToHtml,
58   notifyPost,
59   nsfwCheck,
60   postToCommentSortType,
61   relTags,
62   restoreScrollPosition,
63   saveCommentRes,
64   saveScrollPosition,
65   setIsoData,
66   setupTippy,
67   showLocal,
68   toast,
69   trendingFetchLimit,
70   updatePersonBlock,
71   wsClient,
72   wsSubscribe,
73 } from "../../utils";
74 import { CommentNodes } from "../comment/comment-nodes";
75 import { DataTypeSelect } from "../common/data-type-select";
76 import { HtmlTags } from "../common/html-tags";
77 import { Icon, Spinner } from "../common/icon";
78 import { ListingTypeSelect } from "../common/listing-type-select";
79 import { Paginator } from "../common/paginator";
80 import { SortSelect } from "../common/sort-select";
81 import { CommunityLink } from "../community/community-link";
82 import { PostListings } from "../post/post-listings";
83 import { SiteSidebar } from "./site-sidebar";
84
85 interface HomeState {
86   trendingCommunities: CommunityView[];
87   siteRes: GetSiteResponse;
88   posts: PostView[];
89   comments: CommentView[];
90   listingType: ListingType;
91   dataType: DataType;
92   sort: SortType;
93   page: number;
94   showSubscribedMobile: boolean;
95   showTrendingMobile: boolean;
96   showSidebarMobile: boolean;
97   subscribedCollapsed: boolean;
98   loading: boolean;
99   tagline: Option<string>;
100 }
101
102 interface HomeProps {
103   listingType: ListingType;
104   dataType: DataType;
105   sort: SortType;
106   page: number;
107 }
108
109 interface UrlParams {
110   listingType?: ListingType;
111   dataType?: string;
112   sort?: SortType;
113   page?: number;
114 }
115
116 export class Home extends Component<any, HomeState> {
117   private isoData = setIsoData(
118     this.context,
119     GetPostsResponse,
120     GetCommentsResponse,
121     ListCommunitiesResponse
122   );
123   private subscription: Subscription;
124   private emptyState: HomeState = {
125     trendingCommunities: [],
126     siteRes: this.isoData.site_res,
127     showSubscribedMobile: false,
128     showTrendingMobile: false,
129     showSidebarMobile: false,
130     subscribedCollapsed: false,
131     loading: true,
132     posts: [],
133     comments: [],
134     listingType: getListingTypeFromProps(
135       this.props,
136       ListingType[
137         this.isoData.site_res.site_view.local_site.default_post_listing_type
138       ]
139     ),
140     dataType: getDataTypeFromProps(this.props),
141     sort: getSortTypeFromProps(this.props),
142     page: getPageFromProps(this.props),
143     tagline: None
144   };
145
146   constructor(props: any, context: any) {
147     super(props, context);
148
149     this.state = this.emptyState;
150     this.handleSortChange = this.handleSortChange.bind(this);
151     this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
152     this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
153     this.handlePageChange = this.handlePageChange.bind(this);
154
155     this.parseMessage = this.parseMessage.bind(this);
156     this.subscription = wsSubscribe(this.parseMessage);
157
158     // Only fetch the data if coming from another route
159     if (this.isoData.path == this.context.router.route.match.url) {
160       let postsRes = Some(this.isoData.routeData[0] as GetPostsResponse);
161       let commentsRes = Some(this.isoData.routeData[1] as GetCommentsResponse);
162       let trendingRes = this.isoData.routeData[2] as ListCommunitiesResponse;
163
164       if (postsRes.isSome()) {
165         this.state = { ...this.state, posts: postsRes.unwrap().posts };
166       }
167
168       if (commentsRes.isSome()) {
169         this.state = { ...this.state, comments: commentsRes.unwrap().comments };
170       }
171
172       if (isBrowser()) {
173         WebSocketService.Instance.send(
174           wsClient.communityJoin({ community_id: 0 })
175         );
176       }
177       const taglines = this.state.siteRes.taglines;
178       this.state = {
179         ...this.state,
180         trendingCommunities: trendingRes.communities,
181         loading: false,
182         tagline: taglines.map(tls => getRandomFromList(tls).content)
183       };
184     } else {
185       this.fetchTrendingCommunities();
186       this.fetchData();
187     }
188   }
189
190   fetchTrendingCommunities() {
191     let listCommunitiesForm = new ListCommunities({
192       type_: Some(ListingType.Local),
193       sort: Some(SortType.Hot),
194       limit: Some(trendingFetchLimit),
195       page: None,
196       auth: auth(false).ok(),
197     });
198     WebSocketService.Instance.send(
199       wsClient.listCommunities(listCommunitiesForm)
200     );
201   }
202
203   componentDidMount() {
204     // This means it hasn't been set up yet
205     if (!this.state.siteRes.site_view.local_site.site_setup) {
206       this.context.router.history.push("/setup");
207     }
208     setupTippy();
209   }
210
211   componentWillUnmount() {
212     saveScrollPosition(this.context);
213     this.subscription.unsubscribe();
214   }
215
216   static getDerivedStateFromProps(
217     props: HomeProps,
218     state: HomeState
219   ): HomeProps {
220     return {
221       listingType: getListingTypeFromProps(props, state.listingType),
222       dataType: getDataTypeFromProps(props),
223       sort: getSortTypeFromProps(props),
224       page: getPageFromProps(props),
225     };
226   }
227
228   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
229     let pathSplit = req.path.split("/");
230     let dataType: DataType = pathSplit[3]
231       ? DataType[pathSplit[3]]
232       : DataType.Post;
233
234     // TODO figure out auth default_listingType, default_sort_type
235     let type_: Option<ListingType> = Some(
236       pathSplit[5]
237         ? ListingType[pathSplit[5]]
238         : UserService.Instance.myUserInfo.match({
239             some: mui =>
240               Object.values(ListingType)[
241                 mui.local_user_view.local_user.default_listing_type
242               ],
243             none: ListingType.Local,
244           })
245     );
246     let sort: Option<SortType> = Some(
247       pathSplit[7]
248         ? SortType[pathSplit[7]]
249         : UserService.Instance.myUserInfo.match({
250             some: mui =>
251               Object.values(SortType)[
252                 mui.local_user_view.local_user.default_sort_type
253               ],
254             none: SortType.Active,
255           })
256     );
257
258     let page = Some(pathSplit[9] ? Number(pathSplit[9]) : 1);
259
260     let promises: Promise<any>[] = [];
261
262     if (dataType == DataType.Post) {
263       let getPostsForm = new GetPosts({
264         community_id: None,
265         community_name: None,
266         type_,
267         page,
268         limit: Some(fetchLimit),
269         sort,
270         saved_only: Some(false),
271         auth: req.auth,
272       });
273
274       promises.push(req.client.getPosts(getPostsForm));
275       promises.push(Promise.resolve());
276     } else {
277       let getCommentsForm = new GetComments({
278         community_id: None,
279         community_name: None,
280         page,
281         limit: Some(fetchLimit),
282         max_depth: None,
283         sort: sort.map(postToCommentSortType),
284         type_,
285         saved_only: Some(false),
286         post_id: None,
287         parent_id: None,
288         auth: req.auth,
289       });
290       promises.push(Promise.resolve());
291       promises.push(req.client.getComments(getCommentsForm));
292     }
293
294     let trendingCommunitiesForm = new ListCommunities({
295       type_: Some(ListingType.Local),
296       sort: Some(SortType.Hot),
297       limit: Some(trendingFetchLimit),
298       page: None,
299       auth: req.auth,
300     });
301     promises.push(req.client.listCommunities(trendingCommunitiesForm));
302
303     return promises;
304   }
305
306   componentDidUpdate(_: any, lastState: HomeState) {
307     if (
308       lastState.listingType !== this.state.listingType ||
309       lastState.dataType !== this.state.dataType ||
310       lastState.sort !== this.state.sort ||
311       lastState.page !== this.state.page
312     ) {
313       this.setState({ loading: true });
314       this.fetchData();
315     }
316   }
317
318   get documentTitle(): string {
319     let siteView = this.state.siteRes.site_view;
320     return this.state.siteRes.site_view.site.description.match({
321       some: desc => `${siteView.site.name} - ${desc}`,
322       none: siteView.site.name,
323     });
324   }
325
326   render() {
327     return (
328       <div className="container-lg">
329         <HtmlTags
330           title={this.documentTitle}
331           path={this.context.router.route.match.url}
332           description={None}
333           image={None}
334         />
335         {this.state.siteRes.site_view.local_site.site_setup && (
336           <div className="row">
337             <main role="main" className="col-12 col-md-8">
338               {this.state.tagline.match({
339                 some: tagline => <div id="tagline" dangerouslySetInnerHTML={mdToHtml(tagline)}></div>,
340                 none: <></>,
341               })}
342               <div className="d-block d-md-none">{this.mobileView()}</div>
343               {this.posts()}
344             </main>
345             <aside className="d-none d-md-block col-md-4">
346               {this.mySidebar()}
347             </aside>
348           </div>
349         )}
350       </div>
351     );
352   }
353
354   get hasFollows(): boolean {
355     return UserService.Instance.myUserInfo.match({
356       some: mui => mui.follows.length > 0,
357       none: false,
358     });
359   }
360
361   mobileView() {
362     let siteRes = this.state.siteRes;
363     let siteView = siteRes.site_view;
364     return (
365       <div className="row">
366         <div className="col-12">
367           {this.hasFollows && (
368             <button
369               className="btn btn-secondary d-inline-block mb-2 mr-3"
370               onClick={linkEvent(this, this.handleShowSubscribedMobile)}
371             >
372               {i18n.t("subscribed")}{" "}
373               <Icon
374                 icon={
375                   this.state.showSubscribedMobile
376                     ? `minus-square`
377                     : `plus-square`
378                 }
379                 classes="icon-inline"
380               />
381             </button>
382           )}
383           <button
384             className="btn btn-secondary d-inline-block mb-2 mr-3"
385             onClick={linkEvent(this, this.handleShowTrendingMobile)}
386           >
387             {i18n.t("trending")}{" "}
388             <Icon
389               icon={
390                 this.state.showTrendingMobile ? `minus-square` : `plus-square`
391               }
392               classes="icon-inline"
393             />
394           </button>
395           <button
396             className="btn btn-secondary d-inline-block mb-2 mr-3"
397             onClick={linkEvent(this, this.handleShowSidebarMobile)}
398           >
399             {i18n.t("sidebar")}{" "}
400             <Icon
401               icon={
402                 this.state.showSidebarMobile ? `minus-square` : `plus-square`
403               }
404               classes="icon-inline"
405             />
406           </button>
407           {this.state.showSidebarMobile && (
408             <SiteSidebar
409               site={siteView.site}
410               admins={Some(siteRes.admins)}
411               counts={Some(siteView.counts)}
412               online={Some(siteRes.online)}
413               showLocal={showLocal(this.isoData)}
414             />
415           )}
416           {this.state.showTrendingMobile && (
417             <div className="col-12 card border-secondary mb-3">
418               <div className="card-body">{this.trendingCommunities()}</div>
419             </div>
420           )}
421           {this.state.showSubscribedMobile && (
422             <div className="col-12 card border-secondary mb-3">
423               <div className="card-body">{this.subscribedCommunities()}</div>
424             </div>
425           )}
426         </div>
427       </div>
428     );
429   }
430
431   mySidebar() {
432     let siteRes = this.state.siteRes;
433     let siteView = siteRes.site_view;
434     return (
435       <div>
436         {!this.state.loading && (
437           <div>
438             <div className="card border-secondary mb-3">
439               <div className="card-body">
440                 {this.trendingCommunities()}
441                 {canCreateCommunity(this.state.siteRes) &&
442                   this.createCommunityButton()}
443                 {this.exploreCommunitiesButton()}
444               </div>
445             </div>
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             {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 = 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 }