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