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