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