]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/home.tsx
Fix tippy on component mount. Fixes #509 (#511)
[lemmy-ui.git] / src / shared / components / home / home.tsx
1 import { Component, linkEvent } from "inferno";
2 import { T } from "inferno-i18next-dess";
3 import { Link } from "inferno-router";
4 import {
5   AddAdminResponse,
6   BanPersonResponse,
7   BlockPersonResponse,
8   CommentReportResponse,
9   CommentResponse,
10   CommentView,
11   CommunityView,
12   GetComments,
13   GetCommentsResponse,
14   GetPosts,
15   GetPostsResponse,
16   GetSiteResponse,
17   ListCommunities,
18   ListCommunitiesResponse,
19   ListingType,
20   PostReportResponse,
21   PostResponse,
22   PostView,
23   SiteResponse,
24   SortType,
25   UserOperation,
26 } from "lemmy-js-client";
27 import { Subscription } from "rxjs";
28 import { i18n } from "../../i18next";
29 import { DataType, InitialFetchRequest } from "../../interfaces";
30 import { UserService, WebSocketService } from "../../services";
31 import {
32   authField,
33   commentsToFlatNodes,
34   createCommentLikeRes,
35   createPostLikeFindRes,
36   editCommentRes,
37   editPostFindRes,
38   fetchLimit,
39   getDataTypeFromProps,
40   getListingTypeFromProps,
41   getPageFromProps,
42   getSortTypeFromProps,
43   mdToHtml,
44   notifyPost,
45   numToSI,
46   restoreScrollPosition,
47   saveCommentRes,
48   saveScrollPosition,
49   setIsoData,
50   setOptionalAuth,
51   setupTippy,
52   showLocal,
53   toast,
54   updatePersonBlock,
55   wsClient,
56   wsJsonToRes,
57   wsSubscribe,
58   wsUserOp,
59 } from "../../utils";
60 import { CommentNodes } from "../comment/comment-nodes";
61 import { BannerIconHeader } from "../common/banner-icon-header";
62 import { DataTypeSelect } from "../common/data-type-select";
63 import { HtmlTags } from "../common/html-tags";
64 import { Icon, Spinner } from "../common/icon";
65 import { ListingTypeSelect } from "../common/listing-type-select";
66 import { Paginator } from "../common/paginator";
67 import { SortSelect } from "../common/sort-select";
68 import { CommunityLink } from "../community/community-link";
69 import { PersonListing } from "../person/person-listing";
70 import { PostListings } from "../post/post-listings";
71 import { SiteForm } from "./site-form";
72
73 interface HomeState {
74   trendingCommunities: CommunityView[];
75   siteRes: GetSiteResponse;
76   showEditSite: boolean;
77   showSubscribedMobile: boolean;
78   showTrendingMobile: boolean;
79   showSidebarMobile: boolean;
80   loading: boolean;
81   posts: PostView[];
82   comments: CommentView[];
83   listingType: ListingType;
84   dataType: DataType;
85   sort: SortType;
86   page: number;
87 }
88
89 interface HomeProps {
90   listingType: ListingType;
91   dataType: DataType;
92   sort: SortType;
93   page: number;
94 }
95
96 interface UrlParams {
97   listingType?: ListingType;
98   dataType?: string;
99   sort?: SortType;
100   page?: number;
101 }
102
103 export class Home extends Component<any, HomeState> {
104   private isoData = setIsoData(this.context);
105   private subscription: Subscription;
106   private emptyState: HomeState = {
107     trendingCommunities: [],
108     siteRes: this.isoData.site_res,
109     showEditSite: false,
110     showSubscribedMobile: false,
111     showTrendingMobile: false,
112     showSidebarMobile: false,
113     loading: true,
114     posts: [],
115     comments: [],
116     listingType: getListingTypeFromProps(this.props),
117     dataType: getDataTypeFromProps(this.props),
118     sort: getSortTypeFromProps(this.props),
119     page: getPageFromProps(this.props),
120   };
121
122   constructor(props: any, context: any) {
123     super(props, context);
124
125     this.state = this.emptyState;
126     this.handleEditCancel = this.handleEditCancel.bind(this);
127     this.handleSortChange = this.handleSortChange.bind(this);
128     this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
129     this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
130     this.handlePageChange = this.handlePageChange.bind(this);
131
132     this.parseMessage = this.parseMessage.bind(this);
133     this.subscription = wsSubscribe(this.parseMessage);
134
135     // Only fetch the data if coming from another route
136     if (this.isoData.path == this.context.router.route.match.url) {
137       if (this.state.dataType == DataType.Post) {
138         this.state.posts = this.isoData.routeData[0].posts;
139       } else {
140         this.state.comments = this.isoData.routeData[0].comments;
141       }
142       this.state.trendingCommunities = this.isoData.routeData[1].communities;
143       this.state.loading = false;
144     } else {
145       this.fetchTrendingCommunities();
146       this.fetchData();
147     }
148   }
149
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     setupTippy();
170   }
171
172   componentWillUnmount() {
173     saveScrollPosition(this.context);
174     this.subscription.unsubscribe();
175     window.isoData.path = undefined;
176   }
177
178   static getDerivedStateFromProps(props: any): HomeProps {
179     return {
180       listingType: getListingTypeFromProps(props),
181       dataType: getDataTypeFromProps(props),
182       sort: getSortTypeFromProps(props),
183       page: getPageFromProps(props),
184     };
185   }
186
187   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
188     let pathSplit = req.path.split("/");
189     let dataType: DataType = pathSplit[3]
190       ? DataType[pathSplit[3]]
191       : DataType.Post;
192
193     // TODO figure out auth default_listingType, default_sort_type
194     let type_: ListingType = pathSplit[5]
195       ? ListingType[pathSplit[5]]
196       : UserService.Instance.myUserInfo
197       ? Object.values(ListingType)[
198           UserService.Instance.myUserInfo.local_user_view.local_user
199             .default_listing_type
200         ]
201       : ListingType.Local;
202     let sort: SortType = pathSplit[7]
203       ? SortType[pathSplit[7]]
204       : UserService.Instance.myUserInfo
205       ? Object.values(SortType)[
206           UserService.Instance.myUserInfo.local_user_view.local_user
207             .default_sort_type
208         ]
209       : SortType.Active;
210
211     let page = pathSplit[9] ? Number(pathSplit[9]) : 1;
212
213     let promises: Promise<any>[] = [];
214
215     if (dataType == DataType.Post) {
216       let getPostsForm: GetPosts = {
217         page,
218         limit: fetchLimit,
219         sort,
220         type_,
221         saved_only: false,
222       };
223       setOptionalAuth(getPostsForm, req.auth);
224       promises.push(req.client.getPosts(getPostsForm));
225     } else {
226       let getCommentsForm: GetComments = {
227         page,
228         limit: fetchLimit,
229         sort,
230         type_,
231         saved_only: false,
232       };
233       setOptionalAuth(getCommentsForm, req.auth);
234       promises.push(req.client.getComments(getCommentsForm));
235     }
236
237     let trendingCommunitiesForm: ListCommunities = {
238       type_: ListingType.Local,
239       sort: SortType.Hot,
240       limit: 6,
241     };
242     promises.push(req.client.listCommunities(trendingCommunitiesForm));
243
244     return promises;
245   }
246
247   componentDidUpdate(_: any, lastState: HomeState) {
248     if (
249       lastState.listingType !== this.state.listingType ||
250       lastState.dataType !== this.state.dataType ||
251       lastState.sort !== this.state.sort ||
252       lastState.page !== this.state.page
253     ) {
254       this.setState({ loading: true });
255       this.fetchData();
256     }
257   }
258
259   get documentTitle(): string {
260     return `${
261       this.state.siteRes.site_view
262         ? this.state.siteRes.site_view.site.description
263           ? `${this.state.siteRes.site_view.site.name} - ${this.state.siteRes.site_view.site.description}`
264           : this.state.siteRes.site_view.site.name
265         : "Lemmy"
266     }`;
267   }
268
269   render() {
270     return (
271       <div class="container">
272         <HtmlTags
273           title={this.documentTitle}
274           path={this.context.router.route.match.url}
275         />
276         {this.state.siteRes.site_view?.site && (
277           <div class="row">
278             <main role="main" class="col-12 col-md-8">
279               <div class="d-block d-md-none">{this.mobileView()}</div>
280               {this.posts()}
281             </main>
282             <aside class="d-none d-md-block col-md-4">{this.mySidebar()}</aside>
283           </div>
284         )}
285       </div>
286     );
287   }
288
289   mobileView() {
290     return (
291       <div class="row">
292         <div class="col-12">
293           {UserService.Instance.myUserInfo &&
294             UserService.Instance.myUserInfo.follows.length > 0 && (
295               <button
296                 class="btn btn-secondary d-inline-block mb-2 mr-3"
297                 onClick={linkEvent(this, this.handleShowSubscribedMobile)}
298               >
299                 {i18n.t("subscribed")}{" "}
300                 <Icon
301                   icon={
302                     this.state.showSubscribedMobile
303                       ? `minus-square`
304                       : `plus-square`
305                   }
306                   classes="icon-inline"
307                 />
308               </button>
309             )}
310           <button
311             class="btn btn-secondary d-inline-block mb-2 mr-3"
312             onClick={linkEvent(this, this.handleShowTrendingMobile)}
313           >
314             {i18n.t("trending")}{" "}
315             <Icon
316               icon={
317                 this.state.showTrendingMobile ? `minus-square` : `plus-square`
318               }
319               classes="icon-inline"
320             />
321           </button>
322           <button
323             class="btn btn-secondary d-inline-block mb-2 mr-3"
324             onClick={linkEvent(this, this.handleShowSidebarMobile)}
325           >
326             {i18n.t("sidebar")}{" "}
327             <Icon
328               icon={
329                 this.state.showSidebarMobile ? `minus-square` : `plus-square`
330               }
331               classes="icon-inline"
332             />
333           </button>
334           {this.state.showSubscribedMobile && (
335             <div class="col-12 card border-secondary mb-3">
336               <div class="card-body">{this.subscribedCommunities()}</div>
337             </div>
338           )}
339           {this.state.showTrendingMobile && (
340             <div class="col-12 card border-secondary mb-3">
341               <div class="card-body">{this.trendingCommunities()}</div>
342             </div>
343           )}
344           {this.state.showSidebarMobile && (
345             <div class="col-12 card border-secondary mb-3">
346               <div class="card-body">{this.sidebar()}</div>
347             </div>
348           )}
349         </div>
350       </div>
351     );
352   }
353
354   mySidebar() {
355     return (
356       <div>
357         {!this.state.loading && (
358           <div>
359             <div class="card border-secondary mb-3">
360               <div class="card-body">
361                 {this.trendingCommunities()}
362                 {this.createCommunityButton()}
363                 {this.exploreCommunitiesButton()}
364               </div>
365             </div>
366
367             {UserService.Instance.myUserInfo &&
368               UserService.Instance.myUserInfo.follows.length > 0 && (
369                 <div class="card border-secondary mb-3">
370                   <div class="card-body">{this.subscribedCommunities()}</div>
371                 </div>
372               )}
373
374             <div class="card border-secondary mb-3">
375               <div class="card-body">{this.sidebar()}</div>
376             </div>
377           </div>
378         )}
379       </div>
380     );
381   }
382
383   createCommunityButton() {
384     return (
385       <Link className="mt-2 btn btn-secondary btn-block" to="/create_community">
386         {i18n.t("create_a_community")}
387       </Link>
388     );
389   }
390
391   exploreCommunitiesButton() {
392     return (
393       <Link className="btn btn-secondary btn-block" to="/communities">
394         {i18n.t("explore_communities")}
395       </Link>
396     );
397   }
398
399   trendingCommunities() {
400     return (
401       <div>
402         <h5>
403           <T i18nKey="trending_communities">
404             #
405             <Link className="text-body" to="/communities">
406               #
407             </Link>
408           </T>
409         </h5>
410         <ul class="list-inline mb-0">
411           {this.state.trendingCommunities.map(cv => (
412             <li class="list-inline-item d-inline-block">
413               <CommunityLink community={cv.community} />
414             </li>
415           ))}
416         </ul>
417       </div>
418     );
419   }
420
421   subscribedCommunities() {
422     return (
423       <div>
424         <h5>
425           <T i18nKey="subscribed_to_communities">
426             #
427             <Link className="text-body" to="/communities">
428               #
429             </Link>
430           </T>
431         </h5>
432         <ul class="list-inline mb-0">
433           {UserService.Instance.myUserInfo.follows.map(cfv => (
434             <li class="list-inline-item d-inline-block">
435               <CommunityLink community={cfv.community} />
436             </li>
437           ))}
438         </ul>
439       </div>
440     );
441   }
442
443   sidebar() {
444     let site = this.state.siteRes.site_view.site;
445     return (
446       <div>
447         {!this.state.showEditSite ? (
448           <div>
449             <div class="mb-2">
450               {this.siteName()}
451               {this.adminButtons()}
452             </div>
453             <BannerIconHeader banner={site.banner} />
454             {this.siteInfo()}
455           </div>
456         ) : (
457           <SiteForm site={site} onCancel={this.handleEditCancel} />
458         )}
459       </div>
460     );
461   }
462
463   updateUrl(paramUpdates: UrlParams) {
464     const listingTypeStr = paramUpdates.listingType || this.state.listingType;
465     const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType];
466     const sortStr = paramUpdates.sort || this.state.sort;
467     const page = paramUpdates.page || this.state.page;
468     this.props.history.push(
469       `/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${page}`
470     );
471   }
472
473   siteInfo() {
474     let site = this.state.siteRes.site_view.site;
475     return (
476       <div>
477         {site.description && <h6>{site.description}</h6>}
478         {site.sidebar && this.siteSidebar()}
479         {this.badges()}
480         {this.admins()}
481       </div>
482     );
483   }
484
485   siteName() {
486     let site = this.state.siteRes.site_view.site;
487     return site.name && <h5 class="mb-0">{site.name}</h5>;
488   }
489
490   admins() {
491     return (
492       <ul class="mt-1 list-inline small mb-0">
493         <li class="list-inline-item">{i18n.t("admins")}:</li>
494         {this.state.siteRes.admins.map(av => (
495           <li class="list-inline-item">
496             <PersonListing person={av.person} />
497           </li>
498         ))}
499       </ul>
500     );
501   }
502
503   badges() {
504     let counts = this.state.siteRes.site_view.counts;
505     return (
506       <ul class="my-2 list-inline">
507         <li className="list-inline-item badge badge-secondary">
508           {i18n.t("number_online", {
509             count: this.state.siteRes.online,
510             formattedCount: numToSI(this.state.siteRes.online),
511           })}
512         </li>
513         <li
514           className="list-inline-item badge badge-secondary pointer"
515           data-tippy-content={i18n.t("active_users_in_the_last_day", {
516             count: counts.users_active_day,
517             formattedCount: numToSI(counts.users_active_day),
518           })}
519         >
520           {i18n.t("number_of_users", {
521             count: counts.users_active_day,
522             formattedCount: numToSI(counts.users_active_day),
523           })}{" "}
524           / {i18n.t("day")}
525         </li>
526         <li
527           className="list-inline-item badge badge-secondary pointer"
528           data-tippy-content={i18n.t("active_users_in_the_last_week", {
529             count: counts.users_active_week,
530             formattedCount: counts.users_active_week,
531           })}
532         >
533           {i18n.t("number_of_users", {
534             count: counts.users_active_week,
535             formattedCount: numToSI(counts.users_active_week),
536           })}{" "}
537           / {i18n.t("week")}
538         </li>
539         <li
540           className="list-inline-item badge badge-secondary pointer"
541           data-tippy-content={i18n.t("active_users_in_the_last_month", {
542             count: counts.users_active_month,
543             formattedCount: counts.users_active_month,
544           })}
545         >
546           {i18n.t("number_of_users", {
547             count: counts.users_active_month,
548             formattedCount: numToSI(counts.users_active_month),
549           })}{" "}
550           / {i18n.t("month")}
551         </li>
552         <li
553           className="list-inline-item badge badge-secondary pointer"
554           data-tippy-content={i18n.t("active_users_in_the_last_six_months", {
555             count: counts.users_active_half_year,
556             formattedCount: counts.users_active_half_year,
557           })}
558         >
559           {i18n.t("number_of_users", {
560             count: counts.users_active_half_year,
561             formattedCount: numToSI(counts.users_active_half_year),
562           })}{" "}
563           / {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
564         </li>
565         <li className="list-inline-item badge badge-secondary">
566           {i18n.t("number_of_users", {
567             count: counts.users,
568             formattedCount: numToSI(counts.users),
569           })}
570         </li>
571         <li className="list-inline-item badge badge-secondary">
572           {i18n.t("number_of_communities", {
573             count: counts.communities,
574             formattedCount: numToSI(counts.communities),
575           })}
576         </li>
577         <li className="list-inline-item badge badge-secondary">
578           {i18n.t("number_of_posts", {
579             count: counts.posts,
580             formattedCount: numToSI(counts.posts),
581           })}
582         </li>
583         <li className="list-inline-item badge badge-secondary">
584           {i18n.t("number_of_comments", {
585             count: counts.comments,
586             formattedCount: numToSI(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.myUserInfo &&
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.myUserInfo &&
724       this.state.siteRes.admins
725         .map(a => a.person.id)
726         .includes(UserService.Instance.myUserInfo.local_user_view.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.ListCommunities) {
811       let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
812       this.state.trendingCommunities = data.communities;
813       this.setState(this.state);
814     } else if (op == UserOperation.EditSite) {
815       let data = wsJsonToRes<SiteResponse>(msg).data;
816       this.state.siteRes.site_view = data.site_view;
817       this.state.showEditSite = false;
818       this.setState(this.state);
819       toast(i18n.t("site_saved"));
820     } else if (op == UserOperation.GetPosts) {
821       let data = wsJsonToRes<GetPostsResponse>(msg).data;
822       this.state.posts = data.posts;
823       this.state.loading = false;
824       this.setState(this.state);
825       restoreScrollPosition(this.context);
826       setupTippy();
827     } else if (op == UserOperation.CreatePost) {
828       let data = wsJsonToRes<PostResponse>(msg).data;
829
830       // NSFW check
831       let nsfw = data.post_view.post.nsfw || data.post_view.community.nsfw;
832       let nsfwCheck =
833         !nsfw ||
834         (nsfw &&
835           UserService.Instance.myUserInfo &&
836           UserService.Instance.myUserInfo.local_user_view.local_user.show_nsfw);
837
838       // Only push these if you're on the first page, and you pass the nsfw check
839       if (this.state.page == 1 && nsfwCheck) {
840         // If you're on subscribed, only push it if you're subscribed.
841         if (this.state.listingType == ListingType.Subscribed) {
842           if (
843             UserService.Instance.myUserInfo.follows
844               .map(c => c.community.id)
845               .includes(data.post_view.community.id)
846           ) {
847             this.state.posts.unshift(data.post_view);
848             if (
849               UserService.Instance.myUserInfo?.local_user_view.local_user
850                 .show_new_post_notifs
851             ) {
852               notifyPost(data.post_view, this.context.router);
853             }
854           }
855         } else if (this.state.listingType == ListingType.Local) {
856           // If you're on the local view, only push it if its local
857           if (data.post_view.post.local) {
858             this.state.posts.unshift(data.post_view);
859             if (
860               UserService.Instance.myUserInfo?.local_user_view.local_user
861                 .show_new_post_notifs
862             ) {
863               notifyPost(data.post_view, this.context.router);
864             }
865           }
866         } else {
867           this.state.posts.unshift(data.post_view);
868           if (
869             UserService.Instance.myUserInfo?.local_user_view.local_user
870               .show_new_post_notifs
871           ) {
872             notifyPost(data.post_view, this.context.router);
873           }
874         }
875         this.setState(this.state);
876       }
877     } else if (
878       op == UserOperation.EditPost ||
879       op == UserOperation.DeletePost ||
880       op == UserOperation.RemovePost ||
881       op == UserOperation.LockPost ||
882       op == UserOperation.StickyPost ||
883       op == UserOperation.SavePost
884     ) {
885       let data = wsJsonToRes<PostResponse>(msg).data;
886       editPostFindRes(data.post_view, this.state.posts);
887       this.setState(this.state);
888     } else if (op == UserOperation.CreatePostLike) {
889       let data = wsJsonToRes<PostResponse>(msg).data;
890       createPostLikeFindRes(data.post_view, this.state.posts);
891       this.setState(this.state);
892     } else if (op == UserOperation.AddAdmin) {
893       let data = wsJsonToRes<AddAdminResponse>(msg).data;
894       this.state.siteRes.admins = data.admins;
895       this.setState(this.state);
896     } else if (op == UserOperation.BanPerson) {
897       let data = wsJsonToRes<BanPersonResponse>(msg).data;
898       let found = this.state.siteRes.banned.find(
899         p => (p.person.id = data.person_view.person.id)
900       );
901
902       // Remove the banned if its found in the list, and the action is an unban
903       if (found && !data.banned) {
904         this.state.siteRes.banned = this.state.siteRes.banned.filter(
905           i => i.person.id !== data.person_view.person.id
906         );
907       } else {
908         this.state.siteRes.banned.push(data.person_view);
909       }
910
911       this.state.posts
912         .filter(p => p.creator.id == data.person_view.person.id)
913         .forEach(p => (p.creator.banned = data.banned));
914
915       this.setState(this.state);
916     } else if (op == UserOperation.GetComments) {
917       let data = wsJsonToRes<GetCommentsResponse>(msg).data;
918       this.state.comments = data.comments;
919       this.state.loading = false;
920       this.setState(this.state);
921     } else if (
922       op == UserOperation.EditComment ||
923       op == UserOperation.DeleteComment ||
924       op == UserOperation.RemoveComment
925     ) {
926       let data = wsJsonToRes<CommentResponse>(msg).data;
927       editCommentRes(data.comment_view, this.state.comments);
928       this.setState(this.state);
929     } else if (op == UserOperation.CreateComment) {
930       let data = wsJsonToRes<CommentResponse>(msg).data;
931
932       // Necessary since it might be a user reply
933       if (data.form_id) {
934         // If you're on subscribed, only push it if you're subscribed.
935         if (this.state.listingType == ListingType.Subscribed) {
936           if (
937             UserService.Instance.myUserInfo.follows
938               .map(c => c.community.id)
939               .includes(data.comment_view.community.id)
940           ) {
941             this.state.comments.unshift(data.comment_view);
942           }
943         } else {
944           this.state.comments.unshift(data.comment_view);
945         }
946         this.setState(this.state);
947       }
948     } else if (op == UserOperation.SaveComment) {
949       let data = wsJsonToRes<CommentResponse>(msg).data;
950       saveCommentRes(data.comment_view, this.state.comments);
951       this.setState(this.state);
952     } else if (op == UserOperation.CreateCommentLike) {
953       let data = wsJsonToRes<CommentResponse>(msg).data;
954       createCommentLikeRes(data.comment_view, this.state.comments);
955       this.setState(this.state);
956     } else if (op == UserOperation.BlockPerson) {
957       let data = wsJsonToRes<BlockPersonResponse>(msg).data;
958       updatePersonBlock(data);
959     } else if (op == UserOperation.CreatePostReport) {
960       let data = wsJsonToRes<PostReportResponse>(msg).data;
961       if (data) {
962         toast(i18n.t("report_created"));
963       }
964     } else if (op == UserOperation.CreateCommentReport) {
965       let data = wsJsonToRes<CommentReportResponse>(msg).data;
966       if (data) {
967         toast(i18n.t("report_created"));
968       }
969     }
970   }
971 }