]> Untitled Git - lemmy-ui.git/blob - src/shared/components/post/post.tsx
Adding Community Language fixes. #783 (#868)
[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         `${res.post_view.post.name} - ${this.state.siteRes.site_view.site.name}`,
321       none: "",
322     });
323   }
324
325   get imageTag(): Option<string> {
326     return this.state.postRes.match({
327       some: res =>
328         res.post_view.post.thumbnail_url.or(
329           res.post_view.post.url.match({
330             some: url => (isImage(url) ? Some(url) : None),
331             none: None,
332           })
333         ),
334       none: None,
335     });
336   }
337
338   get descriptionTag(): Option<string> {
339     return this.state.postRes.andThen(r => r.post_view.post.body);
340   }
341
342   render() {
343     return (
344       <div className="container-lg">
345         {this.state.loading ? (
346           <h5>
347             <Spinner large />
348           </h5>
349         ) : (
350           this.state.postRes.match({
351             some: res => (
352               <div className="row">
353                 <div className="col-12 col-md-8 mb-3">
354                   <HtmlTags
355                     title={this.documentTitle}
356                     path={this.context.router.route.match.url}
357                     image={this.imageTag}
358                     description={this.descriptionTag}
359                   />
360                   <PostListing
361                     post_view={res.post_view}
362                     duplicates={this.state.crossPosts}
363                     showBody
364                     showCommunity
365                     moderators={Some(res.moderators)}
366                     admins={Some(this.state.siteRes.admins)}
367                     enableDownvotes={enableDownvotes(this.state.siteRes)}
368                     enableNsfw={enableNsfw(this.state.siteRes)}
369                     allLanguages={this.state.siteRes.all_languages}
370                     siteLanguages={this.state.siteRes.discussion_languages}
371                   />
372                   <div ref={this.state.commentSectionRef} className="mb-2" />
373                   <CommentForm
374                     node={Right(res.post_view.post.id)}
375                     disabled={res.post_view.post.locked}
376                     allLanguages={this.state.siteRes.all_languages}
377                     siteLanguages={this.state.siteRes.discussion_languages}
378                   />
379                   <div className="d-block d-md-none">
380                     <button
381                       className="btn btn-secondary d-inline-block mb-2 mr-3"
382                       onClick={linkEvent(this, this.handleShowSidebarMobile)}
383                     >
384                       {i18n.t("sidebar")}{" "}
385                       <Icon
386                         icon={
387                           this.state.showSidebarMobile
388                             ? `minus-square`
389                             : `plus-square`
390                         }
391                         classes="icon-inline"
392                       />
393                     </button>
394                     {this.state.showSidebarMobile && this.sidebar()}
395                   </div>
396                   {this.sortRadios()}
397                   {this.state.commentViewType == CommentViewType.Tree &&
398                     this.commentsTree()}
399                   {this.state.commentViewType == CommentViewType.Flat &&
400                     this.commentsFlat()}
401                 </div>
402                 <div className="d-none d-md-block col-md-4">
403                   {this.sidebar()}
404                 </div>
405               </div>
406             ),
407             none: <></>,
408           })
409         )}
410       </div>
411     );
412   }
413
414   sortRadios() {
415     return (
416       <>
417         <div className="btn-group btn-group-toggle flex-wrap mr-3 mb-2">
418           <label
419             className={`btn btn-outline-secondary pointer ${
420               CommentSortType[this.state.commentSort] === CommentSortType.Hot &&
421               "active"
422             }`}
423           >
424             {i18n.t("hot")}
425             <input
426               type="radio"
427               value={CommentSortType.Hot}
428               checked={this.state.commentSort === CommentSortType.Hot}
429               onChange={linkEvent(this, this.handleCommentSortChange)}
430             />
431           </label>
432           <label
433             className={`btn btn-outline-secondary pointer ${
434               CommentSortType[this.state.commentSort] === CommentSortType.Top &&
435               "active"
436             }`}
437           >
438             {i18n.t("top")}
439             <input
440               type="radio"
441               value={CommentSortType.Top}
442               checked={this.state.commentSort === CommentSortType.Top}
443               onChange={linkEvent(this, this.handleCommentSortChange)}
444             />
445           </label>
446           <label
447             className={`btn btn-outline-secondary pointer ${
448               CommentSortType[this.state.commentSort] === CommentSortType.New &&
449               "active"
450             }`}
451           >
452             {i18n.t("new")}
453             <input
454               type="radio"
455               value={CommentSortType.New}
456               checked={this.state.commentSort === CommentSortType.New}
457               onChange={linkEvent(this, this.handleCommentSortChange)}
458             />
459           </label>
460           <label
461             className={`btn btn-outline-secondary pointer ${
462               CommentSortType[this.state.commentSort] === CommentSortType.Old &&
463               "active"
464             }`}
465           >
466             {i18n.t("old")}
467             <input
468               type="radio"
469               value={CommentSortType.Old}
470               checked={this.state.commentSort === CommentSortType.Old}
471               onChange={linkEvent(this, this.handleCommentSortChange)}
472             />
473           </label>
474         </div>
475         <div className="btn-group btn-group-toggle flex-wrap mb-2">
476           <label
477             className={`btn btn-outline-secondary pointer ${
478               this.state.commentViewType === CommentViewType.Flat && "active"
479             }`}
480           >
481             {i18n.t("chat")}
482             <input
483               type="radio"
484               value={CommentViewType.Flat}
485               checked={this.state.commentViewType === CommentViewType.Flat}
486               onChange={linkEvent(this, this.handleCommentViewTypeChange)}
487             />
488           </label>
489         </div>
490       </>
491     );
492   }
493
494   commentsFlat() {
495     // These are already sorted by new
496     return this.state.commentsRes.match({
497       some: commentsRes =>
498         this.state.postRes.match({
499           some: postRes => (
500             <div>
501               <CommentNodes
502                 nodes={commentsToFlatNodes(commentsRes.comments)}
503                 viewType={this.state.commentViewType}
504                 maxCommentsShown={Some(this.state.maxCommentsShown)}
505                 noIndent
506                 locked={postRes.post_view.post.locked}
507                 moderators={Some(postRes.moderators)}
508                 admins={Some(this.state.siteRes.admins)}
509                 enableDownvotes={enableDownvotes(this.state.siteRes)}
510                 showContext
511                 allLanguages={this.state.siteRes.all_languages}
512                 siteLanguages={this.state.siteRes.discussion_languages}
513               />
514             </div>
515           ),
516           none: <></>,
517         }),
518       none: <></>,
519     });
520   }
521
522   sidebar() {
523     return this.state.postRes.match({
524       some: res => (
525         <div className="mb-3">
526           <Sidebar
527             community_view={res.community_view}
528             moderators={res.moderators}
529             admins={this.state.siteRes.admins}
530             online={res.online}
531             enableNsfw={enableNsfw(this.state.siteRes)}
532             showIcon
533             allLanguages={this.state.siteRes.all_languages}
534             siteLanguages={this.state.siteRes.discussion_languages}
535             communityLanguages={None}
536           />
537         </div>
538       ),
539       none: <></>,
540     });
541   }
542
543   handleCommentSortChange(i: Post, event: any) {
544     i.setState({
545       commentSort: CommentSortType[event.target.value],
546       commentViewType: CommentViewType.Tree,
547     });
548     i.fetchPost();
549   }
550
551   handleCommentViewTypeChange(i: Post, event: any) {
552     i.setState({
553       commentViewType: Number(event.target.value),
554       commentSort: CommentSortType.New,
555       commentTree: buildCommentsTree(
556         i.state.commentsRes.map(r => r.comments).unwrapOr([]),
557         i.state.commentId.isSome()
558       ),
559     });
560   }
561
562   handleShowSidebarMobile(i: Post) {
563     i.setState({ showSidebarMobile: !i.state.showSidebarMobile });
564   }
565
566   handleViewPost(i: Post) {
567     i.state.postRes.match({
568       some: res =>
569         i.context.router.history.push(`/post/${res.post_view.post.id}`),
570       none: void 0,
571     });
572   }
573
574   handleViewContext(i: Post) {
575     i.state.commentsRes.match({
576       some: res =>
577         i.context.router.history.push(
578           `/comment/${getCommentParentId(res.comments[0].comment).unwrap()}`
579         ),
580       none: void 0,
581     });
582   }
583
584   commentsTree() {
585     let showContextButton = toOption(this.state.commentTree[0]).match({
586       some: comment => getDepthFromComment(comment.comment_view.comment) > 0,
587       none: false,
588     });
589
590     return this.state.postRes.match({
591       some: res => (
592         <div>
593           {this.state.commentId.isSome() && (
594             <>
595               <button
596                 className="pl-0 d-block btn btn-link text-muted"
597                 onClick={linkEvent(this, this.handleViewPost)}
598               >
599                 {i18n.t("view_all_comments")} âž”
600               </button>
601               {showContextButton && (
602                 <button
603                   className="pl-0 d-block btn btn-link text-muted"
604                   onClick={linkEvent(this, this.handleViewContext)}
605                 >
606                   {i18n.t("show_context")} âž”
607                 </button>
608               )}
609             </>
610           )}
611           <CommentNodes
612             nodes={this.state.commentTree}
613             viewType={this.state.commentViewType}
614             maxCommentsShown={Some(this.state.maxCommentsShown)}
615             locked={res.post_view.post.locked}
616             moderators={Some(res.moderators)}
617             admins={Some(this.state.siteRes.admins)}
618             enableDownvotes={enableDownvotes(this.state.siteRes)}
619             allLanguages={this.state.siteRes.all_languages}
620             siteLanguages={this.state.siteRes.discussion_languages}
621           />
622         </div>
623       ),
624       none: <></>,
625     });
626   }
627
628   parseMessage(msg: any) {
629     let op = wsUserOp(msg);
630     console.log(msg);
631     if (msg.error) {
632       toast(i18n.t(msg.error), "danger");
633       return;
634     } else if (msg.reconnect) {
635       this.state.postRes.match({
636         some: res => {
637           let postId = res.post_view.post.id;
638           WebSocketService.Instance.send(
639             wsClient.postJoin({ post_id: postId })
640           );
641           WebSocketService.Instance.send(
642             wsClient.getPost({
643               id: Some(postId),
644               comment_id: None,
645               auth: auth(false).ok(),
646             })
647           );
648         },
649         none: void 0,
650       });
651     } else if (op == UserOperation.GetPost) {
652       let data = wsJsonToRes<GetPostResponse>(msg, GetPostResponse);
653       this.setState({ postRes: Some(data) });
654
655       // join the rooms
656       WebSocketService.Instance.send(
657         wsClient.postJoin({ post_id: data.post_view.post.id })
658       );
659       WebSocketService.Instance.send(
660         wsClient.communityJoin({
661           community_id: data.community_view.community.id,
662         })
663       );
664
665       // Get cross-posts
666       // TODO move this into initial fetch and refetch
667       this.fetchCrossPosts();
668       setupTippy();
669       if (this.state.commentId.isNone()) restoreScrollPosition(this.context);
670
671       if (this.checkScrollIntoCommentsParam) {
672         this.scrollIntoCommentSection();
673       }
674     } else if (op == UserOperation.GetComments) {
675       let data = wsJsonToRes<GetCommentsResponse>(msg, GetCommentsResponse);
676       // You might need to append here, since this could be building more comments from a tree fetch
677       this.state.commentsRes.match({
678         some: res => {
679           // Remove the first comment, since it is the parent
680           let newComments = data.comments;
681           newComments.shift();
682           res.comments.push(...newComments);
683         },
684         none: () => this.setState({ commentsRes: Some(data) }),
685       });
686       // this.state.commentsRes = Some(data);
687       this.setState({
688         commentTree: buildCommentsTree(
689           this.state.commentsRes.map(r => r.comments).unwrapOr([]),
690           this.state.commentId.isSome()
691         ),
692         loading: false,
693       });
694       this.setState(this.state);
695     } else if (op == UserOperation.CreateComment) {
696       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
697
698       // Don't get comments from the post room, if the creator is blocked
699       let creatorBlocked = UserService.Instance.myUserInfo
700         .map(m => m.person_blocks)
701         .unwrapOr([])
702         .map(pb => pb.target.id)
703         .includes(data.comment_view.creator.id);
704
705       // Necessary since it might be a user reply, which has the recipients, to avoid double
706       if (data.recipient_ids.length == 0 && !creatorBlocked) {
707         this.state.postRes.match({
708           some: postRes =>
709             this.state.commentsRes.match({
710               some: commentsRes => {
711                 commentsRes.comments.unshift(data.comment_view);
712                 insertCommentIntoTree(
713                   this.state.commentTree,
714                   data.comment_view,
715                   this.state.commentId.isSome()
716                 );
717                 postRes.post_view.counts.comments++;
718               },
719               none: void 0,
720             }),
721           none: void 0,
722         });
723         this.setState(this.state);
724         setupTippy();
725       }
726     } else if (
727       op == UserOperation.EditComment ||
728       op == UserOperation.DeleteComment ||
729       op == UserOperation.RemoveComment
730     ) {
731       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
732       editCommentRes(
733         data.comment_view,
734         this.state.commentsRes.map(r => r.comments).unwrapOr([])
735       );
736       this.setState(this.state);
737       setupTippy();
738     } else if (op == UserOperation.SaveComment) {
739       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
740       saveCommentRes(
741         data.comment_view,
742         this.state.commentsRes.map(r => r.comments).unwrapOr([])
743       );
744       this.setState(this.state);
745       setupTippy();
746     } else if (op == UserOperation.CreateCommentLike) {
747       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
748       createCommentLikeRes(
749         data.comment_view,
750         this.state.commentsRes.map(r => r.comments).unwrapOr([])
751       );
752       this.setState(this.state);
753     } else if (op == UserOperation.CreatePostLike) {
754       let data = wsJsonToRes<PostResponse>(msg, PostResponse);
755       this.state.postRes.match({
756         some: res => createPostLikeRes(data.post_view, res.post_view),
757         none: void 0,
758       });
759       this.setState(this.state);
760     } else if (
761       op == UserOperation.EditPost ||
762       op == UserOperation.DeletePost ||
763       op == UserOperation.RemovePost ||
764       op == UserOperation.LockPost ||
765       op == UserOperation.FeaturePost ||
766       op == UserOperation.SavePost
767     ) {
768       let data = wsJsonToRes<PostResponse>(msg, PostResponse);
769       this.state.postRes.match({
770         some: res => (res.post_view = data.post_view),
771         none: void 0,
772       });
773       this.setState(this.state);
774       setupTippy();
775     } else if (
776       op == UserOperation.EditCommunity ||
777       op == UserOperation.DeleteCommunity ||
778       op == UserOperation.RemoveCommunity ||
779       op == UserOperation.FollowCommunity
780     ) {
781       let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
782       this.state.postRes.match({
783         some: res => {
784           res.community_view = data.community_view;
785           res.post_view.community = data.community_view.community;
786           this.setState(this.state);
787         },
788         none: void 0,
789       });
790     } else if (op == UserOperation.BanFromCommunity) {
791       let data = wsJsonToRes<BanFromCommunityResponse>(
792         msg,
793         BanFromCommunityResponse
794       );
795       this.state.postRes.match({
796         some: postRes =>
797           this.state.commentsRes.match({
798             some: commentsRes => {
799               commentsRes.comments
800                 .filter(c => c.creator.id == data.person_view.person.id)
801                 .forEach(c => (c.creator_banned_from_community = data.banned));
802               if (postRes.post_view.creator.id == data.person_view.person.id) {
803                 postRes.post_view.creator_banned_from_community = data.banned;
804               }
805               this.setState(this.state);
806             },
807             none: void 0,
808           }),
809         none: void 0,
810       });
811     } else if (op == UserOperation.AddModToCommunity) {
812       let data = wsJsonToRes<AddModToCommunityResponse>(
813         msg,
814         AddModToCommunityResponse
815       );
816       this.state.postRes.match({
817         some: res => {
818           res.moderators = data.moderators;
819           this.setState(this.state);
820         },
821         none: void 0,
822       });
823     } else if (op == UserOperation.BanPerson) {
824       let data = wsJsonToRes<BanPersonResponse>(msg, BanPersonResponse);
825       this.state.postRes.match({
826         some: postRes =>
827           this.state.commentsRes.match({
828             some: commentsRes => {
829               commentsRes.comments
830                 .filter(c => c.creator.id == data.person_view.person.id)
831                 .forEach(c => (c.creator.banned = data.banned));
832               if (postRes.post_view.creator.id == data.person_view.person.id) {
833                 postRes.post_view.creator.banned = data.banned;
834               }
835               this.setState(this.state);
836             },
837             none: void 0,
838           }),
839         none: void 0,
840       });
841     } else if (op == UserOperation.AddAdmin) {
842       let data = wsJsonToRes<AddAdminResponse>(msg, AddAdminResponse);
843       this.setState(s => ((s.siteRes.admins = data.admins), s));
844     } else if (op == UserOperation.Search) {
845       let data = wsJsonToRes<SearchResponse>(msg, SearchResponse);
846       let xPosts = data.posts.filter(
847         p => p.post.id != Number(this.props.match.params.id)
848       );
849       this.setState({ crossPosts: xPosts.length > 0 ? Some(xPosts) : None });
850     } else if (op == UserOperation.LeaveAdmin) {
851       let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse);
852       this.setState({ siteRes: data });
853     } else if (op == UserOperation.TransferCommunity) {
854       let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse);
855       this.state.postRes.match({
856         some: res => {
857           res.community_view = data.community_view;
858           res.post_view.community = data.community_view.community;
859           res.moderators = data.moderators;
860           this.setState(this.state);
861         },
862         none: void 0,
863       });
864     } else if (op == UserOperation.BlockPerson) {
865       let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
866       updatePersonBlock(data);
867     } else if (op == UserOperation.CreatePostReport) {
868       let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
869       if (data) {
870         toast(i18n.t("report_created"));
871       }
872     } else if (op == UserOperation.CreateCommentReport) {
873       let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
874       if (data) {
875         toast(i18n.t("report_created"));
876       }
877     } else if (
878       op == UserOperation.PurgePerson ||
879       op == UserOperation.PurgePost ||
880       op == UserOperation.PurgeComment ||
881       op == UserOperation.PurgeCommunity
882     ) {
883       let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse);
884       if (data.success) {
885         toast(i18n.t("purge_success"));
886         this.context.router.history.push(`/`);
887       }
888     }
889   }
890 }