]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/home.tsx
Collapse sidebar on mobile. Fixes #335 (#340)
[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               </div>
377             </div>
378
379             {UserService.Instance.localUserView &&
380               this.state.subscribedCommunities.length > 0 && (
381                 <div class="card border-secondary mb-3">
382                   <div class="card-body">{this.subscribedCommunities()}</div>
383                 </div>
384               )}
385
386             <div class="card border-secondary mb-3">
387               <div class="card-body">{this.sidebar()}</div>
388             </div>
389           </div>
390         )}
391       </div>
392     );
393   }
394
395   createCommunityButton() {
396     return (
397       <Link className="btn btn-secondary btn-block" to="/create_community">
398         {i18n.t("create_a_community")}
399       </Link>
400     );
401   }
402
403   trendingCommunities() {
404     return (
405       <div>
406         <h5>
407           <T i18nKey="trending_communities">
408             #
409             <Link className="text-body" to="/communities">
410               #
411             </Link>
412           </T>
413         </h5>
414         <ul class="list-inline">
415           {this.state.trendingCommunities.map(cv => (
416             <li class="list-inline-item d-inline-block">
417               <CommunityLink community={cv.community} />
418             </li>
419           ))}
420         </ul>
421       </div>
422     );
423   }
424
425   subscribedCommunities() {
426     return (
427       <div>
428         <h5>
429           <T i18nKey="subscribed_to_communities">
430             #
431             <Link className="text-body" to="/communities">
432               #
433             </Link>
434           </T>
435         </h5>
436         <ul class="list-inline mb-0">
437           {this.state.subscribedCommunities.map(cfv => (
438             <li class="list-inline-item d-inline-block">
439               <CommunityLink community={cfv.community} />
440             </li>
441           ))}
442         </ul>
443       </div>
444     );
445   }
446
447   sidebar() {
448     let site = this.state.siteRes.site_view.site;
449     return (
450       <div>
451         {!this.state.showEditSite ? (
452           <div>
453             <div class="mb-2">
454               {this.siteName()}
455               {this.adminButtons()}
456             </div>
457             <BannerIconHeader banner={site.banner} />
458             {this.siteInfo()}
459           </div>
460         ) : (
461           <SiteForm site={site} onCancel={this.handleEditCancel} />
462         )}
463       </div>
464     );
465   }
466
467   updateUrl(paramUpdates: UrlParams) {
468     const listingTypeStr = paramUpdates.listingType || this.state.listingType;
469     const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType];
470     const sortStr = paramUpdates.sort || this.state.sort;
471     const page = paramUpdates.page || this.state.page;
472     this.props.history.push(
473       `/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${page}`
474     );
475   }
476
477   siteInfo() {
478     let site = this.state.siteRes.site_view.site;
479     return (
480       <div>
481         {site.description && <h6>{site.description}</h6>}
482         {site.sidebar && this.siteSidebar()}
483         {this.badges()}
484         {this.admins()}
485       </div>
486     );
487   }
488
489   siteName() {
490     let site = this.state.siteRes.site_view.site;
491     return site.name && <h5 class="mb-0">{site.name}</h5>;
492   }
493
494   admins() {
495     return (
496       <ul class="mt-1 list-inline small mb-0">
497         <li class="list-inline-item">{i18n.t("admins")}:</li>
498         {this.state.siteRes.admins.map(av => (
499           <li class="list-inline-item">
500             <PersonListing person={av.person} />
501           </li>
502         ))}
503       </ul>
504     );
505   }
506
507   badges() {
508     let counts = this.state.siteRes.site_view.counts;
509     return (
510       <ul class="my-2 list-inline">
511         <li className="list-inline-item badge badge-secondary">
512           {i18n.t("number_online", { count: this.state.siteRes.online })}
513         </li>
514         <li
515           className="list-inline-item badge badge-secondary pointer"
516           data-tippy-content={`${i18n.t("number_of_users", {
517             count: counts.users_active_day,
518           })} ${i18n.t("active_in_the_last")} ${i18n.t("day")}`}
519         >
520           {i18n.t("number_of_users", {
521             count: 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           })} ${i18n.t("active_in_the_last")} ${i18n.t("week")}`}
530         >
531           {i18n.t("number_of_users", {
532             count: counts.users_active_week,
533           })}{" "}
534           / {i18n.t("week")}
535         </li>
536         <li
537           className="list-inline-item badge badge-secondary pointer"
538           data-tippy-content={`${i18n.t("number_of_users", {
539             count: counts.users_active_month,
540           })} ${i18n.t("active_in_the_last")} ${i18n.t("month")}`}
541         >
542           {i18n.t("number_of_users", {
543             count: counts.users_active_month,
544           })}{" "}
545           / {i18n.t("month")}
546         </li>
547         <li
548           className="list-inline-item badge badge-secondary pointer"
549           data-tippy-content={`${i18n.t("number_of_users", {
550             count: counts.users_active_half_year,
551           })} ${i18n.t("active_in_the_last")} ${i18n.t("number_of_months", {
552             count: 6,
553           })}`}
554         >
555           {i18n.t("number_of_users", {
556             count: counts.users_active_half_year,
557           })}{" "}
558           / {i18n.t("number_of_months", { count: 6 })}
559         </li>
560         <li className="list-inline-item badge badge-secondary">
561           {i18n.t("number_of_users", {
562             count: counts.users,
563           })}
564         </li>
565         <li className="list-inline-item badge badge-secondary">
566           {i18n.t("number_of_communities", {
567             count: counts.communities,
568           })}
569         </li>
570         <li className="list-inline-item badge badge-secondary">
571           {i18n.t("number_of_posts", {
572             count: counts.posts,
573           })}
574         </li>
575         <li className="list-inline-item badge badge-secondary">
576           {i18n.t("number_of_comments", {
577             count: counts.comments,
578           })}
579         </li>
580         <li className="list-inline-item">
581           <Link className="badge badge-secondary" to="/modlog">
582             {i18n.t("modlog")}
583           </Link>
584         </li>
585       </ul>
586     );
587   }
588
589   adminButtons() {
590     return (
591       this.canAdmin && (
592         <ul class="list-inline mb-1 text-muted font-weight-bold">
593           <li className="list-inline-item-action">
594             <button
595               class="btn btn-link d-inline-block text-muted"
596               onClick={linkEvent(this, this.handleEditClick)}
597               aria-label={i18n.t("edit")}
598               data-tippy-content={i18n.t("edit")}
599             >
600               <Icon icon="edit" classes="icon-inline" />
601             </button>
602           </li>
603         </ul>
604       )
605     );
606   }
607
608   siteSidebar() {
609     return (
610       <div
611         className="md-div"
612         dangerouslySetInnerHTML={mdToHtml(
613           this.state.siteRes.site_view.site.sidebar
614         )}
615       />
616     );
617   }
618
619   posts() {
620     return (
621       <div class="main-content-wrapper">
622         {this.state.loading ? (
623           <h5>
624             <Spinner large />
625           </h5>
626         ) : (
627           <div>
628             {this.selects()}
629             {this.listings()}
630             <Paginator
631               page={this.state.page}
632               onChange={this.handlePageChange}
633             />
634           </div>
635         )}
636       </div>
637     );
638   }
639
640   listings() {
641     let site = this.state.siteRes.site_view.site;
642     return this.state.dataType == DataType.Post ? (
643       <PostListings
644         posts={this.state.posts}
645         showCommunity
646         removeDuplicates
647         enableDownvotes={site.enable_downvotes}
648         enableNsfw={site.enable_nsfw}
649       />
650     ) : (
651       <CommentNodes
652         nodes={commentsToFlatNodes(this.state.comments)}
653         noIndent
654         showCommunity
655         showContext
656         enableDownvotes={site.enable_downvotes}
657       />
658     );
659   }
660
661   selects() {
662     return (
663       <div className="mb-3">
664         <span class="mr-3">
665           <DataTypeSelect
666             type_={this.state.dataType}
667             onChange={this.handleDataTypeChange}
668           />
669         </span>
670         <span class="mr-3">
671           <ListingTypeSelect
672             type_={this.state.listingType}
673             showLocal={showLocal(this.isoData)}
674             onChange={this.handleListingTypeChange}
675           />
676         </span>
677         <span class="mr-2">
678           <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
679         </span>
680         {this.state.listingType == ListingType.All && (
681           <a
682             href={`/feeds/all.xml?sort=${this.state.sort}`}
683             rel="noopener"
684             title="RSS"
685           >
686             <Icon icon="rss" classes="text-muted small" />
687           </a>
688         )}
689         {this.state.listingType == ListingType.Local && (
690           <a
691             href={`/feeds/local.xml?sort=${this.state.sort}`}
692             rel="noopener"
693             title="RSS"
694           >
695             <Icon icon="rss" classes="text-muted small" />
696           </a>
697         )}
698         {UserService.Instance.localUserView &&
699           this.state.listingType == ListingType.Subscribed && (
700             <a
701               href={`/feeds/front/${UserService.Instance.auth}.xml?sort=${this.state.sort}`}
702               title="RSS"
703               rel="noopener"
704             >
705               <Icon icon="rss" classes="text-muted small" />
706             </a>
707           )}
708       </div>
709     );
710   }
711
712   get canAdmin(): boolean {
713     return (
714       UserService.Instance.localUserView &&
715       this.state.siteRes.admins
716         .map(a => a.person.id)
717         .includes(UserService.Instance.localUserView.person.id)
718     );
719   }
720
721   handleEditClick(i: Home) {
722     i.state.showEditSite = true;
723     i.setState(i.state);
724   }
725
726   handleEditCancel() {
727     this.state.showEditSite = false;
728     this.setState(this.state);
729   }
730
731   handleShowSubscribedMobile(i: Home) {
732     i.state.showSubscribedMobile = !i.state.showSubscribedMobile;
733     i.setState(i.state);
734   }
735
736   handleShowTrendingMobile(i: Home) {
737     i.state.showTrendingMobile = !i.state.showTrendingMobile;
738     i.setState(i.state);
739   }
740
741   handleShowSidebarMobile(i: Home) {
742     i.state.showSidebarMobile = !i.state.showSidebarMobile;
743     i.setState(i.state);
744   }
745
746   handlePageChange(page: number) {
747     this.updateUrl({ page });
748     window.scrollTo(0, 0);
749   }
750
751   handleSortChange(val: SortType) {
752     this.updateUrl({ sort: val, page: 1 });
753     window.scrollTo(0, 0);
754   }
755
756   handleListingTypeChange(val: ListingType) {
757     this.updateUrl({ listingType: val, page: 1 });
758     window.scrollTo(0, 0);
759   }
760
761   handleDataTypeChange(val: DataType) {
762     this.updateUrl({ dataType: DataType[val], page: 1 });
763     window.scrollTo(0, 0);
764   }
765
766   fetchData() {
767     if (this.state.dataType == DataType.Post) {
768       let getPostsForm: GetPosts = {
769         page: this.state.page,
770         limit: fetchLimit,
771         sort: this.state.sort,
772         type_: this.state.listingType,
773         saved_only: false,
774         auth: authField(false),
775       };
776       WebSocketService.Instance.send(wsClient.getPosts(getPostsForm));
777     } else {
778       let getCommentsForm: GetComments = {
779         page: this.state.page,
780         limit: fetchLimit,
781         sort: this.state.sort,
782         type_: this.state.listingType,
783         saved_only: false,
784         auth: authField(false),
785       };
786       WebSocketService.Instance.send(wsClient.getComments(getCommentsForm));
787     }
788   }
789
790   parseMessage(msg: any) {
791     let op = wsUserOp(msg);
792     console.log(msg);
793     if (msg.error) {
794       toast(i18n.t(msg.error), "danger");
795       return;
796     } else if (msg.reconnect) {
797       WebSocketService.Instance.send(
798         wsClient.communityJoin({ community_id: 0 })
799       );
800       this.fetchData();
801     } else if (op == UserOperation.GetFollowedCommunities) {
802       let data = wsJsonToRes<GetFollowedCommunitiesResponse>(msg).data;
803       this.state.subscribedCommunities = data.communities;
804       this.setState(this.state);
805     } else if (op == UserOperation.ListCommunities) {
806       let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
807       this.state.trendingCommunities = data.communities;
808       this.setState(this.state);
809     } else if (op == UserOperation.EditSite) {
810       let data = wsJsonToRes<SiteResponse>(msg).data;
811       this.state.siteRes.site_view = data.site_view;
812       this.state.showEditSite = false;
813       this.setState(this.state);
814       toast(i18n.t("site_saved"));
815     } else if (op == UserOperation.GetPosts) {
816       let data = wsJsonToRes<GetPostsResponse>(msg).data;
817       this.state.posts = data.posts;
818       this.state.loading = false;
819       this.setState(this.state);
820       restoreScrollPosition(this.context);
821       setupTippy();
822     } else if (op == UserOperation.CreatePost) {
823       let data = wsJsonToRes<PostResponse>(msg).data;
824
825       // NSFW check
826       let nsfw = data.post_view.post.nsfw || data.post_view.community.nsfw;
827       let nsfwCheck =
828         !nsfw ||
829         (nsfw &&
830           UserService.Instance.localUserView &&
831           UserService.Instance.localUserView.local_user.show_nsfw);
832
833       // Only push these if you're on the first page, and you pass the nsfw check
834       if (this.state.page == 1 && nsfwCheck) {
835         // If you're on subscribed, only push it if you're subscribed.
836         if (this.state.listingType == ListingType.Subscribed) {
837           if (
838             this.state.subscribedCommunities
839               .map(c => c.community.id)
840               .includes(data.post_view.community.id)
841           ) {
842             this.state.posts.unshift(data.post_view);
843             notifyPost(data.post_view, this.context.router);
844           }
845         } else if (this.state.listingType == ListingType.Local) {
846           // If you're on the local view, only push it if its local
847           if (data.post_view.post.local) {
848             this.state.posts.unshift(data.post_view);
849             notifyPost(data.post_view, this.context.router);
850           }
851         } else {
852           this.state.posts.unshift(data.post_view);
853           notifyPost(data.post_view, this.context.router);
854         }
855         this.setState(this.state);
856       }
857     } else if (
858       op == UserOperation.EditPost ||
859       op == UserOperation.DeletePost ||
860       op == UserOperation.RemovePost ||
861       op == UserOperation.LockPost ||
862       op == UserOperation.StickyPost ||
863       op == UserOperation.SavePost
864     ) {
865       let data = wsJsonToRes<PostResponse>(msg).data;
866       editPostFindRes(data.post_view, this.state.posts);
867       this.setState(this.state);
868     } else if (op == UserOperation.CreatePostLike) {
869       let data = wsJsonToRes<PostResponse>(msg).data;
870       createPostLikeFindRes(data.post_view, this.state.posts);
871       this.setState(this.state);
872     } else if (op == UserOperation.AddAdmin) {
873       let data = wsJsonToRes<AddAdminResponse>(msg).data;
874       this.state.siteRes.admins = data.admins;
875       this.setState(this.state);
876     } else if (op == UserOperation.BanPerson) {
877       let data = wsJsonToRes<BanPersonResponse>(msg).data;
878       let found = this.state.siteRes.banned.find(
879         p => (p.person.id = data.person_view.person.id)
880       );
881
882       // Remove the banned if its found in the list, and the action is an unban
883       if (found && !data.banned) {
884         this.state.siteRes.banned = this.state.siteRes.banned.filter(
885           i => i.person.id !== data.person_view.person.id
886         );
887       } else {
888         this.state.siteRes.banned.push(data.person_view);
889       }
890
891       this.state.posts
892         .filter(p => p.creator.id == data.person_view.person.id)
893         .forEach(p => (p.creator.banned = data.banned));
894
895       this.setState(this.state);
896     } else if (op == UserOperation.GetComments) {
897       let data = wsJsonToRes<GetCommentsResponse>(msg).data;
898       this.state.comments = data.comments;
899       this.state.loading = false;
900       this.setState(this.state);
901     } else if (
902       op == UserOperation.EditComment ||
903       op == UserOperation.DeleteComment ||
904       op == UserOperation.RemoveComment
905     ) {
906       let data = wsJsonToRes<CommentResponse>(msg).data;
907       editCommentRes(data.comment_view, this.state.comments);
908       this.setState(this.state);
909     } else if (op == UserOperation.CreateComment) {
910       let data = wsJsonToRes<CommentResponse>(msg).data;
911
912       // Necessary since it might be a user reply
913       if (data.form_id) {
914         // If you're on subscribed, only push it if you're subscribed.
915         if (this.state.listingType == ListingType.Subscribed) {
916           if (
917             this.state.subscribedCommunities
918               .map(c => c.community.id)
919               .includes(data.comment_view.community.id)
920           ) {
921             this.state.comments.unshift(data.comment_view);
922           }
923         } else {
924           this.state.comments.unshift(data.comment_view);
925         }
926         this.setState(this.state);
927       }
928     } else if (op == UserOperation.SaveComment) {
929       let data = wsJsonToRes<CommentResponse>(msg).data;
930       saveCommentRes(data.comment_view, this.state.comments);
931       this.setState(this.state);
932     } else if (op == UserOperation.CreateCommentLike) {
933       let data = wsJsonToRes<CommentResponse>(msg).data;
934       createCommentLikeRes(data.comment_view, this.state.comments);
935       this.setState(this.state);
936     }
937   }
938 }