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