]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/home.tsx
Adding site sidebar for remote communities. Fixes #626 (#640)
[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   notifyPost,
44   relTags,
45   restoreScrollPosition,
46   saveCommentRes,
47   saveScrollPosition,
48   setIsoData,
49   setOptionalAuth,
50   setupTippy,
51   showLocal,
52   toast,
53   updatePersonBlock,
54   wsClient,
55   wsJsonToRes,
56   wsSubscribe,
57   wsUserOp,
58 } from "../../utils";
59 import { CommentNodes } from "../comment/comment-nodes";
60 import { DataTypeSelect } from "../common/data-type-select";
61 import { HtmlTags } from "../common/html-tags";
62 import { Icon, Spinner } from "../common/icon";
63 import { ListingTypeSelect } from "../common/listing-type-select";
64 import { Paginator } from "../common/paginator";
65 import { SortSelect } from "../common/sort-select";
66 import { CommunityLink } from "../community/community-link";
67 import { PostListings } from "../post/post-listings";
68 import { SiteSidebar } from "./site-sidebar";
69
70 interface HomeState {
71   trendingCommunities: CommunityView[];
72   siteRes: GetSiteResponse;
73   showSubscribedMobile: boolean;
74   showTrendingMobile: boolean;
75   showSidebarMobile: boolean;
76   subscribedCollapsed: boolean;
77   loading: boolean;
78   posts: PostView[];
79   comments: CommentView[];
80   listingType: ListingType;
81   dataType: DataType;
82   sort: SortType;
83   page: number;
84 }
85
86 interface HomeProps {
87   listingType: ListingType;
88   dataType: DataType;
89   sort: SortType;
90   page: number;
91 }
92
93 interface UrlParams {
94   listingType?: ListingType;
95   dataType?: string;
96   sort?: SortType;
97   page?: number;
98 }
99
100 export class Home extends Component<any, HomeState> {
101   private isoData = setIsoData(this.context);
102   private subscription: Subscription;
103   private emptyState: HomeState = {
104     trendingCommunities: [],
105     siteRes: this.isoData.site_res,
106     showSubscribedMobile: false,
107     showTrendingMobile: false,
108     showSidebarMobile: false,
109     subscribedCollapsed: false,
110     loading: true,
111     posts: [],
112     comments: [],
113     listingType: getListingTypeFromProps(this.props),
114     dataType: getDataTypeFromProps(this.props),
115     sort: getSortTypeFromProps(this.props),
116     page: getPageFromProps(this.props),
117   };
118
119   constructor(props: any, context: any) {
120     super(props, context);
121
122     this.state = this.emptyState;
123     this.handleSortChange = this.handleSortChange.bind(this);
124     this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
125     this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
126     this.handlePageChange = this.handlePageChange.bind(this);
127
128     this.parseMessage = this.parseMessage.bind(this);
129     this.subscription = wsSubscribe(this.parseMessage);
130
131     // Only fetch the data if coming from another route
132     if (this.isoData.path == this.context.router.route.match.url) {
133       if (this.state.dataType == DataType.Post) {
134         this.state.posts = this.isoData.routeData[0].posts;
135       } else {
136         this.state.comments = this.isoData.routeData[0].comments;
137       }
138       this.state.trendingCommunities = this.isoData.routeData[1].communities;
139       this.state.loading = false;
140     } else {
141       this.fetchTrendingCommunities();
142       this.fetchData();
143     }
144   }
145
146   fetchTrendingCommunities() {
147     let listCommunitiesForm: ListCommunities = {
148       type_: ListingType.Local,
149       sort: SortType.Hot,
150       limit: 6,
151       auth: authField(false),
152     };
153     WebSocketService.Instance.send(
154       wsClient.listCommunities(listCommunitiesForm)
155     );
156   }
157
158   componentDidMount() {
159     // This means it hasn't been set up yet
160     if (!this.state.siteRes.site_view) {
161       this.context.router.history.push("/setup");
162     }
163
164     WebSocketService.Instance.send(wsClient.communityJoin({ community_id: 0 }));
165     setupTippy();
166   }
167
168   componentWillUnmount() {
169     saveScrollPosition(this.context);
170     this.subscription.unsubscribe();
171     window.isoData.path = undefined;
172   }
173
174   static getDerivedStateFromProps(props: any): HomeProps {
175     return {
176       listingType: getListingTypeFromProps(props),
177       dataType: getDataTypeFromProps(props),
178       sort: getSortTypeFromProps(props),
179       page: getPageFromProps(props),
180     };
181   }
182
183   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
184     let pathSplit = req.path.split("/");
185     let dataType: DataType = pathSplit[3]
186       ? DataType[pathSplit[3]]
187       : DataType.Post;
188
189     // TODO figure out auth default_listingType, default_sort_type
190     let type_: ListingType = pathSplit[5]
191       ? ListingType[pathSplit[5]]
192       : UserService.Instance.myUserInfo
193       ? Object.values(ListingType)[
194           UserService.Instance.myUserInfo.local_user_view.local_user
195             .default_listing_type
196         ]
197       : ListingType.Local;
198     let sort: SortType = pathSplit[7]
199       ? SortType[pathSplit[7]]
200       : UserService.Instance.myUserInfo
201       ? Object.values(SortType)[
202           UserService.Instance.myUserInfo.local_user_view.local_user
203             .default_sort_type
204         ]
205       : SortType.Active;
206
207     let page = pathSplit[9] ? Number(pathSplit[9]) : 1;
208
209     let promises: Promise<any>[] = [];
210
211     if (dataType == DataType.Post) {
212       let getPostsForm: GetPosts = {
213         page,
214         limit: fetchLimit,
215         sort,
216         type_,
217         saved_only: false,
218       };
219       setOptionalAuth(getPostsForm, req.auth);
220       promises.push(req.client.getPosts(getPostsForm));
221     } else {
222       let getCommentsForm: GetComments = {
223         page,
224         limit: fetchLimit,
225         sort,
226         type_,
227         saved_only: false,
228       };
229       setOptionalAuth(getCommentsForm, req.auth);
230       promises.push(req.client.getComments(getCommentsForm));
231     }
232
233     let trendingCommunitiesForm: ListCommunities = {
234       type_: ListingType.Local,
235       sort: SortType.Hot,
236       limit: 6,
237     };
238     setOptionalAuth(trendingCommunitiesForm, req.auth);
239     promises.push(req.client.listCommunities(trendingCommunitiesForm));
240
241     return promises;
242   }
243
244   componentDidUpdate(_: any, lastState: HomeState) {
245     if (
246       lastState.listingType !== this.state.listingType ||
247       lastState.dataType !== this.state.dataType ||
248       lastState.sort !== this.state.sort ||
249       lastState.page !== this.state.page
250     ) {
251       this.setState({ loading: true });
252       this.fetchData();
253     }
254   }
255
256   get documentTitle(): string {
257     return `${
258       this.state.siteRes.site_view
259         ? this.state.siteRes.site_view.site.description
260           ? `${this.state.siteRes.site_view.site.name} - ${this.state.siteRes.site_view.site.description}`
261           : this.state.siteRes.site_view.site.name
262         : "Lemmy"
263     }`;
264   }
265
266   render() {
267     return (
268       <div class="container">
269         <HtmlTags
270           title={this.documentTitle}
271           path={this.context.router.route.match.url}
272         />
273         {this.state.siteRes.site_view?.site && (
274           <div class="row">
275             <main role="main" class="col-12 col-md-8">
276               <div class="d-block d-md-none">{this.mobileView()}</div>
277               {this.posts()}
278             </main>
279             <aside class="d-none d-md-block col-md-4">{this.mySidebar()}</aside>
280           </div>
281         )}
282       </div>
283     );
284   }
285
286   mobileView() {
287     let siteRes = this.state.siteRes;
288     return (
289       <div class="row">
290         <div class="col-12">
291           {UserService.Instance.myUserInfo &&
292             UserService.Instance.myUserInfo.follows.length > 0 && (
293               <button
294                 class="btn btn-secondary d-inline-block mb-2 mr-3"
295                 onClick={linkEvent(this, this.handleShowSubscribedMobile)}
296               >
297                 {i18n.t("subscribed")}{" "}
298                 <Icon
299                   icon={
300                     this.state.showSubscribedMobile
301                       ? `minus-square`
302                       : `plus-square`
303                   }
304                   classes="icon-inline"
305                 />
306               </button>
307             )}
308           <button
309             class="btn btn-secondary d-inline-block mb-2 mr-3"
310             onClick={linkEvent(this, this.handleShowTrendingMobile)}
311           >
312             {i18n.t("trending")}{" "}
313             <Icon
314               icon={
315                 this.state.showTrendingMobile ? `minus-square` : `plus-square`
316               }
317               classes="icon-inline"
318             />
319           </button>
320           <button
321             class="btn btn-secondary d-inline-block mb-2 mr-3"
322             onClick={linkEvent(this, this.handleShowSidebarMobile)}
323           >
324             {i18n.t("sidebar")}{" "}
325             <Icon
326               icon={
327                 this.state.showSidebarMobile ? `minus-square` : `plus-square`
328               }
329               classes="icon-inline"
330             />
331           </button>
332           {this.state.showSidebarMobile && (
333             <SiteSidebar
334               site={siteRes.site_view.site}
335               admins={siteRes.admins}
336               counts={siteRes.site_view.counts}
337               online={siteRes.online}
338             />
339           )}
340           {this.state.showTrendingMobile && (
341             <div class="col-12 card border-secondary mb-3">
342               <div class="card-body">{this.trendingCommunities()}</div>
343             </div>
344           )}
345           {this.state.showSubscribedMobile && (
346             <div class="col-12 card border-secondary mb-3">
347               <div class="card-body">{this.subscribedCommunities()}</div>
348             </div>
349           )}
350         </div>
351       </div>
352     );
353   }
354
355   mySidebar() {
356     let siteRes = this.state.siteRes;
357     return (
358       <div>
359         {!this.state.loading && (
360           <div>
361             <div class="card border-secondary mb-3">
362               <div class="card-body">
363                 {this.trendingCommunities()}
364                 {this.createCommunityButton()}
365                 {this.exploreCommunitiesButton()}
366               </div>
367             </div>
368
369             <SiteSidebar
370               site={siteRes.site_view.site}
371               admins={siteRes.admins}
372               counts={siteRes.site_view.counts}
373               online={siteRes.online}
374             />
375
376             {UserService.Instance.myUserInfo &&
377               UserService.Instance.myUserInfo.follows.length > 0 && (
378                 <div class="card border-secondary mb-3">
379                   <div class="card-body">{this.subscribedCommunities()}</div>
380                 </div>
381               )}
382           </div>
383         )}
384       </div>
385     );
386   }
387
388   createCommunityButton() {
389     return (
390       <Link className="mt-2 btn btn-secondary btn-block" to="/create_community">
391         {i18n.t("create_a_community")}
392       </Link>
393     );
394   }
395
396   exploreCommunitiesButton() {
397     return (
398       <Link className="btn btn-secondary btn-block" to="/communities">
399         {i18n.t("explore_communities")}
400       </Link>
401     );
402   }
403
404   trendingCommunities() {
405     return (
406       <div>
407         <h5>
408           <T i18nKey="trending_communities">
409             #
410             <Link className="text-body" to="/communities">
411               #
412             </Link>
413           </T>
414         </h5>
415         <ul class="list-inline mb-0">
416           {this.state.trendingCommunities.map(cv => (
417             <li class="list-inline-item d-inline-block">
418               <CommunityLink community={cv.community} />
419             </li>
420           ))}
421         </ul>
422       </div>
423     );
424   }
425
426   subscribedCommunities() {
427     return (
428       <div>
429         <h5>
430           <T class="d-inline" i18nKey="subscribed_to_communities">
431             #
432             <Link className="text-body" to="/communities">
433               #
434             </Link>
435           </T>
436           <button
437             class="btn btn-sm text-muted"
438             onClick={linkEvent(this, this.handleCollapseSubscribe)}
439             aria-label={i18n.t("collapse")}
440             data-tippy-content={i18n.t("collapse")}
441           >
442             {this.state.subscribedCollapsed ? (
443               <Icon icon="plus-square" classes="icon-inline" />
444             ) : (
445               <Icon icon="minus-square" classes="icon-inline" />
446             )}
447           </button>
448         </h5>
449         {!this.state.subscribedCollapsed && (
450           <ul class="list-inline mb-0">
451             {UserService.Instance.myUserInfo.follows.map(cfv => (
452               <li class="list-inline-item d-inline-block">
453                 <CommunityLink community={cfv.community} />
454               </li>
455             ))}
456           </ul>
457         )}
458       </div>
459     );
460   }
461
462   updateUrl(paramUpdates: UrlParams) {
463     const listingTypeStr = paramUpdates.listingType || this.state.listingType;
464     const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType];
465     const sortStr = paramUpdates.sort || this.state.sort;
466     const page = paramUpdates.page || this.state.page;
467     this.props.history.push(
468       `/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${page}`
469     );
470   }
471
472   posts() {
473     return (
474       <div class="main-content-wrapper">
475         {this.state.loading ? (
476           <h5>
477             <Spinner large />
478           </h5>
479         ) : (
480           <div>
481             {this.selects()}
482             {this.listings()}
483             <Paginator
484               page={this.state.page}
485               onChange={this.handlePageChange}
486             />
487           </div>
488         )}
489       </div>
490     );
491   }
492
493   listings() {
494     let site = this.state.siteRes.site_view.site;
495     return this.state.dataType == DataType.Post ? (
496       <PostListings
497         posts={this.state.posts}
498         showCommunity
499         removeDuplicates
500         enableDownvotes={site.enable_downvotes}
501         enableNsfw={site.enable_nsfw}
502       />
503     ) : (
504       <CommentNodes
505         nodes={commentsToFlatNodes(this.state.comments)}
506         noIndent
507         showCommunity
508         showContext
509         enableDownvotes={site.enable_downvotes}
510       />
511     );
512   }
513
514   selects() {
515     let allRss = `/feeds/all.xml?sort=${this.state.sort}`;
516     let localRss = `/feeds/local.xml?sort=${this.state.sort}`;
517     let frontRss = UserService.Instance.myUserInfo
518       ? `/feeds/front/${UserService.Instance.auth}.xml?sort=${this.state.sort}`
519       : "";
520
521     return (
522       <div className="mb-3">
523         <span class="mr-3">
524           <DataTypeSelect
525             type_={this.state.dataType}
526             onChange={this.handleDataTypeChange}
527           />
528         </span>
529         <span class="mr-3">
530           <ListingTypeSelect
531             type_={this.state.listingType}
532             showLocal={showLocal(this.isoData)}
533             onChange={this.handleListingTypeChange}
534           />
535         </span>
536         <span class="mr-2">
537           <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
538         </span>
539         {this.state.listingType == ListingType.All && (
540           <>
541             <a href={allRss} rel={relTags} title="RSS">
542               <Icon icon="rss" classes="text-muted small" />
543             </a>
544             <link rel="alternate" type="application/atom+xml" href={allRss} />
545           </>
546         )}
547         {this.state.listingType == ListingType.Local && (
548           <>
549             <a href={localRss} rel={relTags} title="RSS">
550               <Icon icon="rss" classes="text-muted small" />
551             </a>
552             <link rel="alternate" type="application/atom+xml" href={localRss} />
553           </>
554         )}
555         {UserService.Instance.myUserInfo &&
556           this.state.listingType == ListingType.Subscribed && (
557             <>
558               <a href={frontRss} title="RSS" rel={relTags}>
559                 <Icon icon="rss" classes="text-muted small" />
560               </a>
561               <link
562                 rel="alternate"
563                 type="application/atom+xml"
564                 href={frontRss}
565               />
566             </>
567           )}
568       </div>
569     );
570   }
571
572   handleShowSubscribedMobile(i: Home) {
573     i.state.showSubscribedMobile = !i.state.showSubscribedMobile;
574     i.setState(i.state);
575   }
576
577   handleShowTrendingMobile(i: Home) {
578     i.state.showTrendingMobile = !i.state.showTrendingMobile;
579     i.setState(i.state);
580   }
581
582   handleShowSidebarMobile(i: Home) {
583     i.state.showSidebarMobile = !i.state.showSidebarMobile;
584     i.setState(i.state);
585   }
586
587   handleCollapseSubscribe(i: Home) {
588     i.state.subscribedCollapsed = !i.state.subscribedCollapsed;
589     i.setState(i.state);
590   }
591
592   handlePageChange(page: number) {
593     this.updateUrl({ page });
594     window.scrollTo(0, 0);
595   }
596
597   handleSortChange(val: SortType) {
598     this.updateUrl({ sort: val, page: 1 });
599     window.scrollTo(0, 0);
600   }
601
602   handleListingTypeChange(val: ListingType) {
603     this.updateUrl({ listingType: val, page: 1 });
604     window.scrollTo(0, 0);
605   }
606
607   handleDataTypeChange(val: DataType) {
608     this.updateUrl({ dataType: DataType[val], page: 1 });
609     window.scrollTo(0, 0);
610   }
611
612   fetchData() {
613     if (this.state.dataType == DataType.Post) {
614       let getPostsForm: GetPosts = {
615         page: this.state.page,
616         limit: fetchLimit,
617         sort: this.state.sort,
618         type_: this.state.listingType,
619         saved_only: false,
620         auth: authField(false),
621       };
622       WebSocketService.Instance.send(wsClient.getPosts(getPostsForm));
623     } else {
624       let getCommentsForm: GetComments = {
625         page: this.state.page,
626         limit: fetchLimit,
627         sort: this.state.sort,
628         type_: this.state.listingType,
629         saved_only: false,
630         auth: authField(false),
631       };
632       WebSocketService.Instance.send(wsClient.getComments(getCommentsForm));
633     }
634   }
635
636   parseMessage(msg: any) {
637     let op = wsUserOp(msg);
638     console.log(msg);
639     if (msg.error) {
640       toast(i18n.t(msg.error), "danger");
641       return;
642     } else if (msg.reconnect) {
643       WebSocketService.Instance.send(
644         wsClient.communityJoin({ community_id: 0 })
645       );
646       this.fetchData();
647     } else if (op == UserOperation.ListCommunities) {
648       let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
649       this.state.trendingCommunities = data.communities;
650       this.setState(this.state);
651     } else if (op == UserOperation.EditSite) {
652       let data = wsJsonToRes<SiteResponse>(msg).data;
653       this.state.siteRes.site_view = data.site_view;
654       this.setState(this.state);
655       toast(i18n.t("site_saved"));
656     } else if (op == UserOperation.GetPosts) {
657       let data = wsJsonToRes<GetPostsResponse>(msg).data;
658       this.state.posts = data.posts;
659       this.state.loading = false;
660       this.setState(this.state);
661       restoreScrollPosition(this.context);
662       setupTippy();
663     } else if (op == UserOperation.CreatePost) {
664       let data = wsJsonToRes<PostResponse>(msg).data;
665
666       // NSFW check
667       let nsfw = data.post_view.post.nsfw || data.post_view.community.nsfw;
668       let nsfwCheck =
669         !nsfw ||
670         (nsfw &&
671           UserService.Instance.myUserInfo &&
672           UserService.Instance.myUserInfo.local_user_view.local_user.show_nsfw);
673
674       // Only push these if you're on the first page, and you pass the nsfw check
675       if (this.state.page == 1 && nsfwCheck) {
676         // If you're on subscribed, only push it if you're subscribed.
677         if (this.state.listingType == ListingType.Subscribed) {
678           if (
679             UserService.Instance.myUserInfo.follows
680               .map(c => c.community.id)
681               .includes(data.post_view.community.id)
682           ) {
683             this.state.posts.unshift(data.post_view);
684             if (
685               UserService.Instance.myUserInfo?.local_user_view.local_user
686                 .show_new_post_notifs
687             ) {
688               notifyPost(data.post_view, this.context.router);
689             }
690           }
691         } else if (this.state.listingType == ListingType.Local) {
692           // If you're on the local view, only push it if its local
693           if (data.post_view.post.local) {
694             this.state.posts.unshift(data.post_view);
695             if (
696               UserService.Instance.myUserInfo?.local_user_view.local_user
697                 .show_new_post_notifs
698             ) {
699               notifyPost(data.post_view, this.context.router);
700             }
701           }
702         } else {
703           this.state.posts.unshift(data.post_view);
704           if (
705             UserService.Instance.myUserInfo?.local_user_view.local_user
706               .show_new_post_notifs
707           ) {
708             notifyPost(data.post_view, this.context.router);
709           }
710         }
711         this.setState(this.state);
712       }
713     } else if (
714       op == UserOperation.EditPost ||
715       op == UserOperation.DeletePost ||
716       op == UserOperation.RemovePost ||
717       op == UserOperation.LockPost ||
718       op == UserOperation.StickyPost ||
719       op == UserOperation.SavePost
720     ) {
721       let data = wsJsonToRes<PostResponse>(msg).data;
722       editPostFindRes(data.post_view, this.state.posts);
723       this.setState(this.state);
724     } else if (op == UserOperation.CreatePostLike) {
725       let data = wsJsonToRes<PostResponse>(msg).data;
726       createPostLikeFindRes(data.post_view, this.state.posts);
727       this.setState(this.state);
728     } else if (op == UserOperation.AddAdmin) {
729       let data = wsJsonToRes<AddAdminResponse>(msg).data;
730       this.state.siteRes.admins = data.admins;
731       this.setState(this.state);
732     } else if (op == UserOperation.BanPerson) {
733       let data = wsJsonToRes<BanPersonResponse>(msg).data;
734       this.state.posts
735         .filter(p => p.creator.id == data.person_view.person.id)
736         .forEach(p => (p.creator.banned = data.banned));
737
738       this.setState(this.state);
739     } else if (op == UserOperation.GetComments) {
740       let data = wsJsonToRes<GetCommentsResponse>(msg).data;
741       this.state.comments = data.comments;
742       this.state.loading = false;
743       this.setState(this.state);
744     } else if (
745       op == UserOperation.EditComment ||
746       op == UserOperation.DeleteComment ||
747       op == UserOperation.RemoveComment
748     ) {
749       let data = wsJsonToRes<CommentResponse>(msg).data;
750       editCommentRes(data.comment_view, this.state.comments);
751       this.setState(this.state);
752     } else if (op == UserOperation.CreateComment) {
753       let data = wsJsonToRes<CommentResponse>(msg).data;
754
755       // Necessary since it might be a user reply
756       if (data.form_id) {
757         // If you're on subscribed, only push it if you're subscribed.
758         if (this.state.listingType == ListingType.Subscribed) {
759           if (
760             UserService.Instance.myUserInfo.follows
761               .map(c => c.community.id)
762               .includes(data.comment_view.community.id)
763           ) {
764             this.state.comments.unshift(data.comment_view);
765           }
766         } else {
767           this.state.comments.unshift(data.comment_view);
768         }
769         this.setState(this.state);
770       }
771     } else if (op == UserOperation.SaveComment) {
772       let data = wsJsonToRes<CommentResponse>(msg).data;
773       saveCommentRes(data.comment_view, this.state.comments);
774       this.setState(this.state);
775     } else if (op == UserOperation.CreateCommentLike) {
776       let data = wsJsonToRes<CommentResponse>(msg).data;
777       createCommentLikeRes(data.comment_view, this.state.comments);
778       this.setState(this.state);
779     } else if (op == UserOperation.BlockPerson) {
780       let data = wsJsonToRes<BlockPersonResponse>(msg).data;
781       updatePersonBlock(data);
782     } else if (op == UserOperation.CreatePostReport) {
783       let data = wsJsonToRes<PostReportResponse>(msg).data;
784       if (data) {
785         toast(i18n.t("report_created"));
786       }
787     } else if (op == UserOperation.CreateCommentReport) {
788       let data = wsJsonToRes<CommentReportResponse>(msg).data;
789       if (data) {
790         toast(i18n.t("report_created"));
791       }
792     }
793   }
794 }