]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/home.tsx
Si simplifier (#418)
[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("number_of_users", {
515             count: counts.users_active_day,
516             formattedCount: counts.users_active_day,
517           })} ${i18n.t("active_in_the_last")} ${i18n.t("day")}`}
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("number_of_users", {
528             count: counts.users_active_week,
529             formattedCount: counts.users_active_week,
530           })} ${i18n.t("active_in_the_last")} ${i18n.t("week")}`}
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("number_of_users", {
541             count: counts.users_active_month,
542             formattedCount: counts.users_active_month,
543           })} ${i18n.t("active_in_the_last")} ${i18n.t("month")}`}
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("number_of_users", {
554             count: counts.users_active_half_year,
555             formattedCount: counts.users_active_half_year,
556           })} ${i18n.t("active_in_the_last")} ${i18n.t("number_of_months", {
557             count: 6,
558             formattedCount: 6,
559           })}`}
560         >
561           {i18n.t("number_of_users", {
562             count: counts.users_active_half_year,
563             formattedCount: numToSI(counts.users_active_half_year),
564           })}{" "}
565           / {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
566         </li>
567         <li className="list-inline-item badge badge-secondary">
568           {i18n.t("number_of_users", {
569             count: counts.users,
570             formattedCount: numToSI(counts.users),
571           })}
572         </li>
573         <li className="list-inline-item badge badge-secondary">
574           {i18n.t("number_of_communities", {
575             count: counts.communities,
576             formattedCount: numToSI(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             formattedCount: numToSI(counts.posts),
583           })}
584         </li>
585         <li className="list-inline-item badge badge-secondary">
586           {i18n.t("number_of_comments", {
587             count: counts.comments,
588             formattedCount: numToSI(counts.comments),
589           })}
590         </li>
591         <li className="list-inline-item">
592           <Link className="badge badge-secondary" to="/modlog">
593             {i18n.t("modlog")}
594           </Link>
595         </li>
596       </ul>
597     );
598   }
599
600   adminButtons() {
601     return (
602       this.canAdmin && (
603         <ul class="list-inline mb-1 text-muted font-weight-bold">
604           <li className="list-inline-item-action">
605             <button
606               class="btn btn-link d-inline-block text-muted"
607               onClick={linkEvent(this, this.handleEditClick)}
608               aria-label={i18n.t("edit")}
609               data-tippy-content={i18n.t("edit")}
610             >
611               <Icon icon="edit" classes="icon-inline" />
612             </button>
613           </li>
614         </ul>
615       )
616     );
617   }
618
619   siteSidebar() {
620     return (
621       <div
622         className="md-div"
623         dangerouslySetInnerHTML={mdToHtml(
624           this.state.siteRes.site_view.site.sidebar
625         )}
626       />
627     );
628   }
629
630   posts() {
631     return (
632       <div class="main-content-wrapper">
633         {this.state.loading ? (
634           <h5>
635             <Spinner large />
636           </h5>
637         ) : (
638           <div>
639             {this.selects()}
640             {this.listings()}
641             <Paginator
642               page={this.state.page}
643               onChange={this.handlePageChange}
644             />
645           </div>
646         )}
647       </div>
648     );
649   }
650
651   listings() {
652     let site = this.state.siteRes.site_view.site;
653     return this.state.dataType == DataType.Post ? (
654       <PostListings
655         posts={this.state.posts}
656         showCommunity
657         removeDuplicates
658         enableDownvotes={site.enable_downvotes}
659         enableNsfw={site.enable_nsfw}
660       />
661     ) : (
662       <CommentNodes
663         nodes={commentsToFlatNodes(this.state.comments)}
664         noIndent
665         showCommunity
666         showContext
667         enableDownvotes={site.enable_downvotes}
668       />
669     );
670   }
671
672   selects() {
673     return (
674       <div className="mb-3">
675         <span class="mr-3">
676           <DataTypeSelect
677             type_={this.state.dataType}
678             onChange={this.handleDataTypeChange}
679           />
680         </span>
681         <span class="mr-3">
682           <ListingTypeSelect
683             type_={this.state.listingType}
684             showLocal={showLocal(this.isoData)}
685             onChange={this.handleListingTypeChange}
686           />
687         </span>
688         <span class="mr-2">
689           <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
690         </span>
691         {this.state.listingType == ListingType.All && (
692           <a
693             href={`/feeds/all.xml?sort=${this.state.sort}`}
694             rel="noopener"
695             title="RSS"
696           >
697             <Icon icon="rss" classes="text-muted small" />
698           </a>
699         )}
700         {this.state.listingType == ListingType.Local && (
701           <a
702             href={`/feeds/local.xml?sort=${this.state.sort}`}
703             rel="noopener"
704             title="RSS"
705           >
706             <Icon icon="rss" classes="text-muted small" />
707           </a>
708         )}
709         {UserService.Instance.myUserInfo &&
710           this.state.listingType == ListingType.Subscribed && (
711             <a
712               href={`/feeds/front/${UserService.Instance.auth}.xml?sort=${this.state.sort}`}
713               title="RSS"
714               rel="noopener"
715             >
716               <Icon icon="rss" classes="text-muted small" />
717             </a>
718           )}
719       </div>
720     );
721   }
722
723   get canAdmin(): boolean {
724     return (
725       UserService.Instance.myUserInfo &&
726       this.state.siteRes.admins
727         .map(a => a.person.id)
728         .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
729     );
730   }
731
732   handleEditClick(i: Home) {
733     i.state.showEditSite = true;
734     i.setState(i.state);
735   }
736
737   handleEditCancel() {
738     this.state.showEditSite = false;
739     this.setState(this.state);
740   }
741
742   handleShowSubscribedMobile(i: Home) {
743     i.state.showSubscribedMobile = !i.state.showSubscribedMobile;
744     i.setState(i.state);
745   }
746
747   handleShowTrendingMobile(i: Home) {
748     i.state.showTrendingMobile = !i.state.showTrendingMobile;
749     i.setState(i.state);
750   }
751
752   handleShowSidebarMobile(i: Home) {
753     i.state.showSidebarMobile = !i.state.showSidebarMobile;
754     i.setState(i.state);
755   }
756
757   handlePageChange(page: number) {
758     this.updateUrl({ page });
759     window.scrollTo(0, 0);
760   }
761
762   handleSortChange(val: SortType) {
763     this.updateUrl({ sort: val, page: 1 });
764     window.scrollTo(0, 0);
765   }
766
767   handleListingTypeChange(val: ListingType) {
768     this.updateUrl({ listingType: val, page: 1 });
769     window.scrollTo(0, 0);
770   }
771
772   handleDataTypeChange(val: DataType) {
773     this.updateUrl({ dataType: DataType[val], page: 1 });
774     window.scrollTo(0, 0);
775   }
776
777   fetchData() {
778     if (this.state.dataType == DataType.Post) {
779       let getPostsForm: GetPosts = {
780         page: this.state.page,
781         limit: fetchLimit,
782         sort: this.state.sort,
783         type_: this.state.listingType,
784         saved_only: false,
785         auth: authField(false),
786       };
787       WebSocketService.Instance.send(wsClient.getPosts(getPostsForm));
788     } else {
789       let getCommentsForm: GetComments = {
790         page: this.state.page,
791         limit: fetchLimit,
792         sort: this.state.sort,
793         type_: this.state.listingType,
794         saved_only: false,
795         auth: authField(false),
796       };
797       WebSocketService.Instance.send(wsClient.getComments(getCommentsForm));
798     }
799   }
800
801   parseMessage(msg: any) {
802     let op = wsUserOp(msg);
803     console.log(msg);
804     if (msg.error) {
805       toast(i18n.t(msg.error), "danger");
806       return;
807     } else if (msg.reconnect) {
808       WebSocketService.Instance.send(
809         wsClient.communityJoin({ community_id: 0 })
810       );
811       this.fetchData();
812     } else if (op == UserOperation.ListCommunities) {
813       let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
814       this.state.trendingCommunities = data.communities;
815       this.setState(this.state);
816     } else if (op == UserOperation.EditSite) {
817       let data = wsJsonToRes<SiteResponse>(msg).data;
818       this.state.siteRes.site_view = data.site_view;
819       this.state.showEditSite = false;
820       this.setState(this.state);
821       toast(i18n.t("site_saved"));
822     } else if (op == UserOperation.GetPosts) {
823       let data = wsJsonToRes<GetPostsResponse>(msg).data;
824       this.state.posts = data.posts;
825       this.state.loading = false;
826       this.setState(this.state);
827       restoreScrollPosition(this.context);
828       setupTippy();
829     } else if (op == UserOperation.CreatePost) {
830       let data = wsJsonToRes<PostResponse>(msg).data;
831
832       // NSFW check
833       let nsfw = data.post_view.post.nsfw || data.post_view.community.nsfw;
834       let nsfwCheck =
835         !nsfw ||
836         (nsfw &&
837           UserService.Instance.myUserInfo &&
838           UserService.Instance.myUserInfo.local_user_view.local_user.show_nsfw);
839
840       // Only push these if you're on the first page, and you pass the nsfw check
841       if (this.state.page == 1 && nsfwCheck) {
842         // If you're on subscribed, only push it if you're subscribed.
843         if (this.state.listingType == ListingType.Subscribed) {
844           if (
845             UserService.Instance.myUserInfo.follows
846               .map(c => c.community.id)
847               .includes(data.post_view.community.id)
848           ) {
849             this.state.posts.unshift(data.post_view);
850             if (
851               UserService.Instance.myUserInfo?.local_user_view.local_user
852                 .show_new_post_notifs
853             ) {
854               notifyPost(data.post_view, this.context.router);
855             }
856           }
857         } else if (this.state.listingType == ListingType.Local) {
858           // If you're on the local view, only push it if its local
859           if (data.post_view.post.local) {
860             this.state.posts.unshift(data.post_view);
861             if (
862               UserService.Instance.myUserInfo?.local_user_view.local_user
863                 .show_new_post_notifs
864             ) {
865               notifyPost(data.post_view, this.context.router);
866             }
867           }
868         } else {
869           this.state.posts.unshift(data.post_view);
870           if (
871             UserService.Instance.myUserInfo?.local_user_view.local_user
872               .show_new_post_notifs
873           ) {
874             notifyPost(data.post_view, this.context.router);
875           }
876         }
877         this.setState(this.state);
878       }
879     } else if (
880       op == UserOperation.EditPost ||
881       op == UserOperation.DeletePost ||
882       op == UserOperation.RemovePost ||
883       op == UserOperation.LockPost ||
884       op == UserOperation.StickyPost ||
885       op == UserOperation.SavePost
886     ) {
887       let data = wsJsonToRes<PostResponse>(msg).data;
888       editPostFindRes(data.post_view, this.state.posts);
889       this.setState(this.state);
890     } else if (op == UserOperation.CreatePostLike) {
891       let data = wsJsonToRes<PostResponse>(msg).data;
892       createPostLikeFindRes(data.post_view, this.state.posts);
893       this.setState(this.state);
894     } else if (op == UserOperation.AddAdmin) {
895       let data = wsJsonToRes<AddAdminResponse>(msg).data;
896       this.state.siteRes.admins = data.admins;
897       this.setState(this.state);
898     } else if (op == UserOperation.BanPerson) {
899       let data = wsJsonToRes<BanPersonResponse>(msg).data;
900       let found = this.state.siteRes.banned.find(
901         p => (p.person.id = data.person_view.person.id)
902       );
903
904       // Remove the banned if its found in the list, and the action is an unban
905       if (found && !data.banned) {
906         this.state.siteRes.banned = this.state.siteRes.banned.filter(
907           i => i.person.id !== data.person_view.person.id
908         );
909       } else {
910         this.state.siteRes.banned.push(data.person_view);
911       }
912
913       this.state.posts
914         .filter(p => p.creator.id == data.person_view.person.id)
915         .forEach(p => (p.creator.banned = data.banned));
916
917       this.setState(this.state);
918     } else if (op == UserOperation.GetComments) {
919       let data = wsJsonToRes<GetCommentsResponse>(msg).data;
920       this.state.comments = data.comments;
921       this.state.loading = false;
922       this.setState(this.state);
923     } else if (
924       op == UserOperation.EditComment ||
925       op == UserOperation.DeleteComment ||
926       op == UserOperation.RemoveComment
927     ) {
928       let data = wsJsonToRes<CommentResponse>(msg).data;
929       editCommentRes(data.comment_view, this.state.comments);
930       this.setState(this.state);
931     } else if (op == UserOperation.CreateComment) {
932       let data = wsJsonToRes<CommentResponse>(msg).data;
933
934       // Necessary since it might be a user reply
935       if (data.form_id) {
936         // If you're on subscribed, only push it if you're subscribed.
937         if (this.state.listingType == ListingType.Subscribed) {
938           if (
939             UserService.Instance.myUserInfo.follows
940               .map(c => c.community.id)
941               .includes(data.comment_view.community.id)
942           ) {
943             this.state.comments.unshift(data.comment_view);
944           }
945         } else {
946           this.state.comments.unshift(data.comment_view);
947         }
948         this.setState(this.state);
949       }
950     } else if (op == UserOperation.SaveComment) {
951       let data = wsJsonToRes<CommentResponse>(msg).data;
952       saveCommentRes(data.comment_view, this.state.comments);
953       this.setState(this.state);
954     } else if (op == UserOperation.CreateCommentLike) {
955       let data = wsJsonToRes<CommentResponse>(msg).data;
956       createCommentLikeRes(data.comment_view, this.state.comments);
957       this.setState(this.state);
958     } else if (op == UserOperation.BlockPerson) {
959       let data = wsJsonToRes<BlockPersonResponse>(msg).data;
960       updatePersonBlock(data);
961     }
962   }
963 }