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