]> Untitled Git - lemmy-ui.git/blob - src/shared/components/post/post.tsx
Change for container divs to container-lg (#813)
[lemmy-ui.git] / src / shared / components / post / post.tsx
1 import { None, Option, Right, Some } from "@sniptt/monads";
2 import autosize from "autosize";
3 import { Component, createRef, linkEvent, RefObject } from "inferno";
4 import {
5   AddAdminResponse,
6   AddModToCommunityResponse,
7   BanFromCommunityResponse,
8   BanPersonResponse,
9   BlockPersonResponse,
10   CommentNode as CommentNodeI,
11   CommentReportResponse,
12   CommentResponse,
13   CommentSortType,
14   CommunityResponse,
15   GetComments,
16   GetCommentsResponse,
17   GetCommunityResponse,
18   GetPost,
19   GetPostResponse,
20   GetSiteResponse,
21   ListingType,
22   PostReportResponse,
23   PostResponse,
24   PostView,
25   PurgeItemResponse,
26   Search,
27   SearchResponse,
28   SearchType,
29   SortType,
30   toOption,
31   UserOperation,
32   wsJsonToRes,
33   wsUserOp,
34 } from "lemmy-js-client";
35 import { Subscription } from "rxjs";
36 import { i18n } from "../../i18next";
37 import { CommentViewType, InitialFetchRequest } from "../../interfaces";
38 import { UserService, WebSocketService } from "../../services";
39 import {
40   auth,
41   buildCommentsTree,
42   commentsToFlatNodes,
43   commentTreeMaxDepth,
44   createCommentLikeRes,
45   createPostLikeRes,
46   debounce,
47   editCommentRes,
48   enableDownvotes,
49   enableNsfw,
50   getCommentIdFromProps,
51   getCommentParentId,
52   getDepthFromComment,
53   getIdFromProps,
54   insertCommentIntoTree,
55   isBrowser,
56   isImage,
57   restoreScrollPosition,
58   saveCommentRes,
59   saveScrollPosition,
60   setIsoData,
61   setupTippy,
62   toast,
63   trendingFetchLimit,
64   updatePersonBlock,
65   wsClient,
66   wsSubscribe,
67 } from "../../utils";
68 import { CommentForm } from "../comment/comment-form";
69 import { CommentNodes } from "../comment/comment-nodes";
70 import { HtmlTags } from "../common/html-tags";
71 import { Icon, Spinner } from "../common/icon";
72 import { Sidebar } from "../community/sidebar";
73 import { PostListing } from "./post-listing";
74
75 const commentsShownInterval = 15;
76
77 interface PostState {
78   postId: Option<number>;
79   commentId: Option<number>;
80   postRes: Option<GetPostResponse>;
81   commentsRes: Option<GetCommentsResponse>;
82   commentTree: CommentNodeI[];
83   commentSort: CommentSortType;
84   commentViewType: CommentViewType;
85   scrolled?: boolean;
86   loading: boolean;
87   crossPosts: Option<PostView[]>;
88   siteRes: GetSiteResponse;
89   commentSectionRef?: RefObject<HTMLDivElement>;
90   showSidebarMobile: boolean;
91   maxCommentsShown: number;
92 }
93
94 export class Post extends Component<any, PostState> {
95   private subscription: Subscription;
96   private isoData = setIsoData(
97     this.context,
98     GetPostResponse,
99     GetCommentsResponse
100   );
101   private commentScrollDebounced: () => void;
102   private emptyState: PostState = {
103     postRes: None,
104     commentsRes: None,
105     postId: getIdFromProps(this.props),
106     commentId: getCommentIdFromProps(this.props),
107     commentTree: [],
108     commentSort: CommentSortType[CommentSortType.Hot],
109     commentViewType: CommentViewType.Tree,
110     scrolled: false,
111     loading: true,
112     crossPosts: None,
113     siteRes: this.isoData.site_res,
114     commentSectionRef: null,
115     showSidebarMobile: false,
116     maxCommentsShown: commentsShownInterval,
117   };
118
119   constructor(props: any, context: any) {
120     super(props, context);
121
122     this.state = this.emptyState;
123
124     this.parseMessage = this.parseMessage.bind(this);
125     this.subscription = wsSubscribe(this.parseMessage);
126
127     this.state = { ...this.state, commentSectionRef: createRef() };
128
129     // Only fetch the data if coming from another route
130     if (this.isoData.path == this.context.router.route.match.url) {
131       this.state = {
132         ...this.state,
133         postRes: Some(this.isoData.routeData[0] as GetPostResponse),
134         commentsRes: Some(this.isoData.routeData[1] as GetCommentsResponse),
135       };
136
137       if (this.state.commentsRes.isSome()) {
138         this.state = {
139           ...this.state,
140           commentTree: buildCommentsTree(
141             this.state.commentsRes.unwrap().comments,
142             this.state.commentId.isSome()
143           ),
144         };
145       }
146
147       this.state = { ...this.state, loading: false };
148
149       if (isBrowser()) {
150         WebSocketService.Instance.send(
151           wsClient.communityJoin({
152             community_id:
153               this.state.postRes.unwrap().community_view.community.id,
154           })
155         );
156
157         this.state.postId.match({
158           some: post_id =>
159             WebSocketService.Instance.send(wsClient.postJoin({ post_id })),
160           none: void 0,
161         });
162
163         this.fetchCrossPosts();
164
165         if (this.checkScrollIntoCommentsParam) {
166           this.scrollIntoCommentSection();
167         }
168       }
169     } else {
170       this.fetchPost();
171     }
172   }
173
174   fetchPost() {
175     this.setState({ commentsRes: None });
176     let postForm = new GetPost({
177       id: this.state.postId,
178       comment_id: this.state.commentId,
179       auth: auth(false).ok(),
180     });
181     WebSocketService.Instance.send(wsClient.getPost(postForm));
182
183     let commentsForm = new GetComments({
184       post_id: this.state.postId,
185       parent_id: this.state.commentId,
186       max_depth: Some(commentTreeMaxDepth),
187       page: None,
188       limit: None,
189       sort: Some(this.state.commentSort),
190       type_: Some(ListingType.All),
191       community_name: None,
192       community_id: None,
193       saved_only: Some(false),
194       auth: auth(false).ok(),
195     });
196     WebSocketService.Instance.send(wsClient.getComments(commentsForm));
197   }
198
199   fetchCrossPosts() {
200     this.state.postRes
201       .andThen(r => r.post_view.post.url)
202       .match({
203         some: url => {
204           let form = new Search({
205             q: url,
206             type_: Some(SearchType.Url),
207             sort: Some(SortType.TopAll),
208             listing_type: Some(ListingType.All),
209             page: Some(1),
210             limit: Some(trendingFetchLimit),
211             community_id: None,
212             community_name: None,
213             creator_id: None,
214             auth: auth(false).ok(),
215           });
216           WebSocketService.Instance.send(wsClient.search(form));
217         },
218         none: void 0,
219       });
220   }
221
222   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
223     let pathSplit = req.path.split("/");
224     let promises: Promise<any>[] = [];
225
226     let pathType = pathSplit[1];
227     let id = Number(pathSplit[2]);
228
229     let postForm = new GetPost({
230       id: None,
231       comment_id: None,
232       auth: req.auth,
233     });
234
235     let commentsForm = new GetComments({
236       post_id: None,
237       parent_id: None,
238       max_depth: Some(commentTreeMaxDepth),
239       page: None,
240       limit: None,
241       sort: Some(CommentSortType.Hot),
242       type_: Some(ListingType.All),
243       community_name: None,
244       community_id: None,
245       saved_only: Some(false),
246       auth: req.auth,
247     });
248
249     // Set the correct id based on the path type
250     if (pathType == "post") {
251       postForm.id = Some(id);
252       commentsForm.post_id = Some(id);
253     } else {
254       postForm.comment_id = Some(id);
255       commentsForm.parent_id = Some(id);
256     }
257
258     promises.push(req.client.getPost(postForm));
259     promises.push(req.client.getComments(commentsForm));
260
261     return promises;
262   }
263
264   componentWillUnmount() {
265     this.subscription.unsubscribe();
266     document.removeEventListener("scroll", this.commentScrollDebounced);
267
268     saveScrollPosition(this.context);
269   }
270
271   componentDidMount() {
272     autosize(document.querySelectorAll("textarea"));
273
274     this.commentScrollDebounced = debounce(this.trackCommentsBoxScrolling, 100);
275     document.addEventListener("scroll", this.commentScrollDebounced);
276   }
277
278   componentDidUpdate(_lastProps: any) {
279     // Necessary if you are on a post and you click another post (same route)
280     if (_lastProps.location.pathname !== _lastProps.history.location.pathname) {
281       // TODO Couldnt get a refresh working. This does for now.
282       location.reload();
283
284       // let currentId = this.props.match.params.id;
285       // WebSocketService.Instance.getPost(currentId);
286       // this.context.refresh();
287       // this.context.router.history.push(_lastProps.location.pathname);
288     }
289   }
290
291   get checkScrollIntoCommentsParam() {
292     return Boolean(
293       new URLSearchParams(this.props.location.search).get("scrollToComments")
294     );
295   }
296
297   scrollIntoCommentSection() {
298     this.state.commentSectionRef.current?.scrollIntoView();
299   }
300
301   isBottom(el: Element): boolean {
302     return el?.getBoundingClientRect().bottom <= window.innerHeight;
303   }
304
305   /**
306    * Shows new comments when scrolling to the bottom of the comments div
307    */
308   trackCommentsBoxScrolling = () => {
309     const wrappedElement = document.getElementsByClassName("comments")[0];
310     if (wrappedElement && this.isBottom(wrappedElement)) {
311       this.setState({
312         maxCommentsShown: this.state.maxCommentsShown + commentsShownInterval,
313       });
314     }
315   };
316
317   get documentTitle(): string {
318     return this.state.postRes.match({
319       some: res =>
320         this.state.siteRes.site_view.match({
321           some: siteView =>
322             `${res.post_view.post.name} - ${siteView.site.name}`,
323           none: "",
324         }),
325       none: "",
326     });
327   }
328
329   get imageTag(): Option<string> {
330     return this.state.postRes.match({
331       some: res =>
332         res.post_view.post.thumbnail_url.or(
333           res.post_view.post.url.match({
334             some: url => (isImage(url) ? Some(url) : None),
335             none: None,
336           })
337         ),
338       none: None,
339     });
340   }
341
342   get descriptionTag(): Option<string> {
343     return this.state.postRes.andThen(r => r.post_view.post.body);
344   }
345
346   render() {
347     return (
348       <div className="container-lg">
349         {this.state.loading ? (
350           <h5>
351             <Spinner large />
352           </h5>
353         ) : (
354           this.state.postRes.match({
355             some: res => (
356               <div className="row">
357                 <div className="col-12 col-md-8 mb-3">
358                   <HtmlTags
359                     title={this.documentTitle}
360                     path={this.context.router.route.match.url}
361                     image={this.imageTag}
362                     description={this.descriptionTag}
363                   />
364                   <PostListing
365                     post_view={res.post_view}
366                     duplicates={this.state.crossPosts}
367                     showBody
368                     showCommunity
369                     moderators={Some(res.moderators)}
370                     admins={Some(this.state.siteRes.admins)}
371                     enableDownvotes={enableDownvotes(this.state.siteRes)}
372                     enableNsfw={enableNsfw(this.state.siteRes)}
373                     allLanguages={this.state.siteRes.all_languages}
374                   />
375                   <div ref={this.state.commentSectionRef} className="mb-2" />
376                   <CommentForm
377                     node={Right(res.post_view.post.id)}
378                     disabled={res.post_view.post.locked}
379                     allLanguages={this.state.siteRes.all_languages}
380                   />
381                   <div className="d-block d-md-none">
382                     <button
383                       className="btn btn-secondary d-inline-block mb-2 mr-3"
384                       onClick={linkEvent(this, this.handleShowSidebarMobile)}
385                     >
386                       {i18n.t("sidebar")}{" "}
387                       <Icon
388                         icon={
389                           this.state.showSidebarMobile
390                             ? `minus-square`
391                             : `plus-square`
392                         }
393                         classes="icon-inline"
394                       />
395                     </button>
396                     {this.state.showSidebarMobile && this.sidebar()}
397                   </div>
398                   {this.sortRadios()}
399                   {this.state.commentViewType == CommentViewType.Tree &&
400                     this.commentsTree()}
401                   {this.state.commentViewType == CommentViewType.Flat &&
402                     this.commentsFlat()}
403                 </div>
404                 <div className="d-none d-md-block col-md-4">
405                   {this.sidebar()}
406                 </div>
407               </div>
408             ),
409             none: <></>,
410           })
411         )}
412       </div>
413     );
414   }
415
416   sortRadios() {
417     return (
418       <>
419         <div className="btn-group btn-group-toggle flex-wrap mr-3 mb-2">
420           <label
421             className={`btn btn-outline-secondary pointer ${
422               CommentSortType[this.state.commentSort] === CommentSortType.Hot &&
423               "active"
424             }`}
425           >
426             {i18n.t("hot")}
427             <input
428               type="radio"
429               value={CommentSortType.Hot}
430               checked={this.state.commentSort === CommentSortType.Hot}
431               onChange={linkEvent(this, this.handleCommentSortChange)}
432             />
433           </label>
434           <label
435             className={`btn btn-outline-secondary pointer ${
436               CommentSortType[this.state.commentSort] === CommentSortType.Top &&
437               "active"
438             }`}
439           >
440             {i18n.t("top")}
441             <input
442               type="radio"
443               value={CommentSortType.Top}
444               checked={this.state.commentSort === CommentSortType.Top}
445               onChange={linkEvent(this, this.handleCommentSortChange)}
446             />
447           </label>
448           <label
449             className={`btn btn-outline-secondary pointer ${
450               CommentSortType[this.state.commentSort] === CommentSortType.New &&
451               "active"
452             }`}
453           >
454             {i18n.t("new")}
455             <input
456               type="radio"
457               value={CommentSortType.New}
458               checked={this.state.commentSort === CommentSortType.New}
459               onChange={linkEvent(this, this.handleCommentSortChange)}
460             />
461           </label>
462           <label
463             className={`btn btn-outline-secondary pointer ${
464               CommentSortType[this.state.commentSort] === CommentSortType.Old &&
465               "active"
466             }`}
467           >
468             {i18n.t("old")}
469             <input
470               type="radio"
471               value={CommentSortType.Old}
472               checked={this.state.commentSort === CommentSortType.Old}
473               onChange={linkEvent(this, this.handleCommentSortChange)}
474             />
475           </label>
476         </div>
477         <div className="btn-group btn-group-toggle flex-wrap mb-2">
478           <label
479             className={`btn btn-outline-secondary pointer ${
480               this.state.commentViewType === CommentViewType.Flat && "active"
481             }`}
482           >
483             {i18n.t("chat")}
484             <input
485               type="radio"
486               value={CommentViewType.Flat}
487               checked={this.state.commentViewType === CommentViewType.Flat}
488               onChange={linkEvent(this, this.handleCommentViewTypeChange)}
489             />
490           </label>
491         </div>
492       </>
493     );
494   }
495
496   commentsFlat() {
497     // These are already sorted by new
498     return this.state.commentsRes.match({
499       some: commentsRes =>
500         this.state.postRes.match({
501           some: postRes => (
502             <div>
503               <CommentNodes
504                 nodes={commentsToFlatNodes(commentsRes.comments)}
505                 viewType={this.state.commentViewType}
506                 maxCommentsShown={Some(this.state.maxCommentsShown)}
507                 noIndent
508                 locked={postRes.post_view.post.locked}
509                 moderators={Some(postRes.moderators)}
510                 admins={Some(this.state.siteRes.admins)}
511                 enableDownvotes={enableDownvotes(this.state.siteRes)}
512                 showContext
513                 allLanguages={this.state.siteRes.all_languages}
514               />
515             </div>
516           ),
517           none: <></>,
518         }),
519       none: <></>,
520     });
521   }
522
523   sidebar() {
524     return this.state.postRes.match({
525       some: res => (
526         <div className="mb-3">
527           <Sidebar
528             community_view={res.community_view}
529             moderators={res.moderators}
530             admins={this.state.siteRes.admins}
531             online={res.online}
532             enableNsfw={enableNsfw(this.state.siteRes)}
533             showIcon
534           />
535         </div>
536       ),
537       none: <></>,
538     });
539   }
540
541   handleCommentSortChange(i: Post, event: any) {
542     i.setState({
543       commentSort: CommentSortType[event.target.value],
544       commentViewType: CommentViewType.Tree,
545     });
546     i.fetchPost();
547   }
548
549   handleCommentViewTypeChange(i: Post, event: any) {
550     i.setState({
551       commentViewType: Number(event.target.value),
552       commentSort: CommentSortType.New,
553       commentTree: buildCommentsTree(
554         i.state.commentsRes.map(r => r.comments).unwrapOr([]),
555         i.state.commentId.isSome()
556       ),
557     });
558   }
559
560   handleShowSidebarMobile(i: Post) {
561     i.setState({ showSidebarMobile: !i.state.showSidebarMobile });
562   }
563
564   handleViewPost(i: Post) {
565     i.state.postRes.match({
566       some: res =>
567         i.context.router.history.push(`/post/${res.post_view.post.id}`),
568       none: void 0,
569     });
570   }
571
572   handleViewContext(i: Post) {
573     i.state.commentsRes.match({
574       some: res =>
575         i.context.router.history.push(
576           `/comment/${getCommentParentId(res.comments[0].comment).unwrap()}`
577         ),
578       none: void 0,
579     });
580   }
581
582   commentsTree() {
583     let showContextButton = toOption(this.state.commentTree[0]).match({
584       some: comment => getDepthFromComment(comment.comment_view.comment) > 0,
585       none: false,
586     });
587
588     return this.state.postRes.match({
589       some: res => (
590         <div>
591           {this.state.commentId.isSome() && (
592             <>
593               <button
594                 className="pl-0 d-block btn btn-link text-muted"
595                 onClick={linkEvent(this, this.handleViewPost)}
596               >
597                 {i18n.t("view_all_comments")} âž”
598               </button>
599               {showContextButton && (
600                 <button
601                   className="pl-0 d-block btn btn-link text-muted"
602                   onClick={linkEvent(this, this.handleViewContext)}
603                 >
604                   {i18n.t("show_context")} âž”
605                 </button>
606               )}
607             </>
608           )}
609           <CommentNodes
610             nodes={this.state.commentTree}
611             viewType={this.state.commentViewType}
612             maxCommentsShown={Some(this.state.maxCommentsShown)}
613             locked={res.post_view.post.locked}
614             moderators={Some(res.moderators)}
615             admins={Some(this.state.siteRes.admins)}
616             enableDownvotes={enableDownvotes(this.state.siteRes)}
617             allLanguages={this.state.siteRes.all_languages}
618           />
619         </div>
620       ),
621       none: <></>,
622     });
623   }
624
625   parseMessage(msg: any) {
626     let op = wsUserOp(msg);
627     console.log(msg);
628     if (msg.error) {
629       toast(i18n.t(msg.error), "danger");
630       return;
631     } else if (msg.reconnect) {
632       this.state.postRes.match({
633         some: res => {
634           let postId = res.post_view.post.id;
635           WebSocketService.Instance.send(
636             wsClient.postJoin({ post_id: postId })
637           );
638           WebSocketService.Instance.send(
639             wsClient.getPost({
640               id: Some(postId),
641               comment_id: None,
642               auth: auth(false).ok(),
643             })
644           );
645         },
646         none: void 0,
647       });
648     } else if (op == UserOperation.GetPost) {
649       let data = wsJsonToRes<GetPostResponse>(msg, GetPostResponse);
650       this.setState({ postRes: Some(data) });
651
652       // join the rooms
653       WebSocketService.Instance.send(
654         wsClient.postJoin({ post_id: data.post_view.post.id })
655       );
656       WebSocketService.Instance.send(
657         wsClient.communityJoin({
658           community_id: data.community_view.community.id,
659         })
660       );
661
662       // Get cross-posts
663       // TODO move this into initial fetch and refetch
664       this.fetchCrossPosts();
665       setupTippy();
666       if (this.state.commentId.isNone()) restoreScrollPosition(this.context);
667
668       if (this.checkScrollIntoCommentsParam) {
669         this.scrollIntoCommentSection();
670       }
671     } else if (op == UserOperation.GetComments) {
672       let data = wsJsonToRes<GetCommentsResponse>(msg, GetCommentsResponse);
673       // You might need to append here, since this could be building more comments from a tree fetch
674       this.state.commentsRes.match({
675         some: res => {
676           // Remove the first comment, since it is the parent
677           let newComments = data.comments;
678           newComments.shift();
679           res.comments.push(...newComments);
680         },
681         none: () => this.setState({ commentsRes: Some(data) }),
682       });
683       // this.state.commentsRes = Some(data);
684       this.setState({
685         commentTree: buildCommentsTree(
686           this.state.commentsRes.map(r => r.comments).unwrapOr([]),
687           this.state.commentId.isSome()
688         ),
689         loading: false,
690       });
691       this.setState(this.state);
692     } else if (op == UserOperation.CreateComment) {
693       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
694
695       // Don't get comments from the post room, if the creator is blocked
696       let creatorBlocked = UserService.Instance.myUserInfo
697         .map(m => m.person_blocks)
698         .unwrapOr([])
699         .map(pb => pb.target.id)
700         .includes(data.comment_view.creator.id);
701
702       // Necessary since it might be a user reply, which has the recipients, to avoid double
703       if (data.recipient_ids.length == 0 && !creatorBlocked) {
704         this.state.postRes.match({
705           some: postRes =>
706             this.state.commentsRes.match({
707               some: commentsRes => {
708                 commentsRes.comments.unshift(data.comment_view);
709                 insertCommentIntoTree(
710                   this.state.commentTree,
711                   data.comment_view,
712                   this.state.commentId.isSome()
713                 );
714                 postRes.post_view.counts.comments++;
715               },
716               none: void 0,
717             }),
718           none: void 0,
719         });
720         this.setState(this.state);
721         setupTippy();
722       }
723     } else if (
724       op == UserOperation.EditComment ||
725       op == UserOperation.DeleteComment ||
726       op == UserOperation.RemoveComment
727     ) {
728       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
729       editCommentRes(
730         data.comment_view,
731         this.state.commentsRes.map(r => r.comments).unwrapOr([])
732       );
733       this.setState(this.state);
734       setupTippy();
735     } else if (op == UserOperation.SaveComment) {
736       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
737       saveCommentRes(
738         data.comment_view,
739         this.state.commentsRes.map(r => r.comments).unwrapOr([])
740       );
741       this.setState(this.state);
742       setupTippy();
743     } else if (op == UserOperation.CreateCommentLike) {
744       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
745       createCommentLikeRes(
746         data.comment_view,
747         this.state.commentsRes.map(r => r.comments).unwrapOr([])
748       );
749       this.setState(this.state);
750     } else if (op == UserOperation.CreatePostLike) {
751       let data = wsJsonToRes<PostResponse>(msg, PostResponse);
752       this.state.postRes.match({
753         some: res => createPostLikeRes(data.post_view, res.post_view),
754         none: void 0,
755       });
756       this.setState(this.state);
757     } else if (
758       op == UserOperation.EditPost ||
759       op == UserOperation.DeletePost ||
760       op == UserOperation.RemovePost ||
761       op == UserOperation.LockPost ||
762       op == UserOperation.StickyPost ||
763       op == UserOperation.SavePost
764     ) {
765       let data = wsJsonToRes<PostResponse>(msg, PostResponse);
766       this.state.postRes.match({
767         some: res => (res.post_view = data.post_view),
768         none: void 0,
769       });
770       this.setState(this.state);
771       setupTippy();
772     } else if (
773       op == UserOperation.EditCommunity ||
774       op == UserOperation.DeleteCommunity ||
775       op == UserOperation.RemoveCommunity ||
776       op == UserOperation.FollowCommunity
777     ) {
778       let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
779       this.state.postRes.match({
780         some: res => {
781           res.community_view = data.community_view;
782           res.post_view.community = data.community_view.community;
783           this.setState(this.state);
784         },
785         none: void 0,
786       });
787     } else if (op == UserOperation.BanFromCommunity) {
788       let data = wsJsonToRes<BanFromCommunityResponse>(
789         msg,
790         BanFromCommunityResponse
791       );
792       this.state.postRes.match({
793         some: postRes =>
794           this.state.commentsRes.match({
795             some: commentsRes => {
796               commentsRes.comments
797                 .filter(c => c.creator.id == data.person_view.person.id)
798                 .forEach(c => (c.creator_banned_from_community = data.banned));
799               if (postRes.post_view.creator.id == data.person_view.person.id) {
800                 postRes.post_view.creator_banned_from_community = data.banned;
801               }
802               this.setState(this.state);
803             },
804             none: void 0,
805           }),
806         none: void 0,
807       });
808     } else if (op == UserOperation.AddModToCommunity) {
809       let data = wsJsonToRes<AddModToCommunityResponse>(
810         msg,
811         AddModToCommunityResponse
812       );
813       this.state.postRes.match({
814         some: res => {
815           res.moderators = data.moderators;
816           this.setState(this.state);
817         },
818         none: void 0,
819       });
820     } else if (op == UserOperation.BanPerson) {
821       let data = wsJsonToRes<BanPersonResponse>(msg, BanPersonResponse);
822       this.state.postRes.match({
823         some: postRes =>
824           this.state.commentsRes.match({
825             some: commentsRes => {
826               commentsRes.comments
827                 .filter(c => c.creator.id == data.person_view.person.id)
828                 .forEach(c => (c.creator.banned = data.banned));
829               if (postRes.post_view.creator.id == data.person_view.person.id) {
830                 postRes.post_view.creator.banned = data.banned;
831               }
832               this.setState(this.state);
833             },
834             none: void 0,
835           }),
836         none: void 0,
837       });
838     } else if (op == UserOperation.AddAdmin) {
839       let data = wsJsonToRes<AddAdminResponse>(msg, AddAdminResponse);
840       this.setState(s => ((s.siteRes.admins = data.admins), s));
841     } else if (op == UserOperation.Search) {
842       let data = wsJsonToRes<SearchResponse>(msg, SearchResponse);
843       let xPosts = data.posts.filter(
844         p => p.post.id != Number(this.props.match.params.id)
845       );
846       this.setState({ crossPosts: xPosts.length > 0 ? Some(xPosts) : None });
847     } else if (op == UserOperation.LeaveAdmin) {
848       let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse);
849       this.setState({ siteRes: data });
850     } else if (op == UserOperation.TransferCommunity) {
851       let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse);
852       this.state.postRes.match({
853         some: res => {
854           res.community_view = data.community_view;
855           res.post_view.community = data.community_view.community;
856           res.moderators = data.moderators;
857           this.setState(this.state);
858         },
859         none: void 0,
860       });
861     } else if (op == UserOperation.BlockPerson) {
862       let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
863       updatePersonBlock(data);
864     } else if (op == UserOperation.CreatePostReport) {
865       let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
866       if (data) {
867         toast(i18n.t("report_created"));
868       }
869     } else if (op == UserOperation.CreateCommentReport) {
870       let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
871       if (data) {
872         toast(i18n.t("report_created"));
873       }
874     } else if (
875       op == UserOperation.PurgePerson ||
876       op == UserOperation.PurgePost ||
877       op == UserOperation.PurgeComment ||
878       op == UserOperation.PurgeCommunity
879     ) {
880       let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse);
881       if (data.success) {
882         toast(i18n.t("purge_success"));
883         this.context.router.history.push(`/`);
884       }
885     }
886   }
887 }