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