]> Untitled Git - lemmy-ui.git/blob - src/shared/components/post/post.tsx
Fixing post_view glitch. Fixes #740 (#741)
[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     } else if (op == UserOperation.SaveComment) {
725       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
726       saveCommentRes(
727         data.comment_view,
728         this.state.commentsRes.map(r => r.comments).unwrapOr([])
729       );
730       this.setState(this.state);
731       setupTippy();
732     } else if (op == UserOperation.CreateCommentLike) {
733       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
734       createCommentLikeRes(
735         data.comment_view,
736         this.state.commentsRes.map(r => r.comments).unwrapOr([])
737       );
738       this.setState(this.state);
739     } else if (op == UserOperation.CreatePostLike) {
740       let data = wsJsonToRes<PostResponse>(msg, PostResponse);
741       this.state.postRes.match({
742         some: res => createPostLikeRes(data.post_view, res.post_view),
743         none: void 0,
744       });
745       this.setState(this.state);
746     } else if (
747       op == UserOperation.EditPost ||
748       op == UserOperation.DeletePost ||
749       op == UserOperation.RemovePost ||
750       op == UserOperation.LockPost ||
751       op == UserOperation.StickyPost ||
752       op == UserOperation.SavePost
753     ) {
754       let data = wsJsonToRes<PostResponse>(msg, PostResponse);
755       this.state.postRes.match({
756         some: res => (res.post_view = data.post_view),
757         none: void 0,
758       });
759       this.setState(this.state);
760       setupTippy();
761     } else if (
762       op == UserOperation.EditCommunity ||
763       op == UserOperation.DeleteCommunity ||
764       op == UserOperation.RemoveCommunity ||
765       op == UserOperation.FollowCommunity
766     ) {
767       let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
768       this.state.postRes.match({
769         some: res => {
770           res.community_view = data.community_view;
771           res.post_view.community = data.community_view.community;
772           this.setState(this.state);
773         },
774         none: void 0,
775       });
776     } else if (op == UserOperation.BanFromCommunity) {
777       let data = wsJsonToRes<BanFromCommunityResponse>(
778         msg,
779         BanFromCommunityResponse
780       );
781       this.state.postRes.match({
782         some: postRes =>
783           this.state.commentsRes.match({
784             some: commentsRes => {
785               commentsRes.comments
786                 .filter(c => c.creator.id == data.person_view.person.id)
787                 .forEach(c => (c.creator_banned_from_community = data.banned));
788               if (postRes.post_view.creator.id == data.person_view.person.id) {
789                 postRes.post_view.creator_banned_from_community = data.banned;
790               }
791               this.setState(this.state);
792             },
793             none: void 0,
794           }),
795         none: void 0,
796       });
797     } else if (op == UserOperation.AddModToCommunity) {
798       let data = wsJsonToRes<AddModToCommunityResponse>(
799         msg,
800         AddModToCommunityResponse
801       );
802       this.state.postRes.match({
803         some: res => {
804           res.moderators = data.moderators;
805           this.setState(this.state);
806         },
807         none: void 0,
808       });
809     } else if (op == UserOperation.BanPerson) {
810       let data = wsJsonToRes<BanPersonResponse>(msg, BanPersonResponse);
811       this.state.postRes.match({
812         some: postRes =>
813           this.state.commentsRes.match({
814             some: commentsRes => {
815               commentsRes.comments
816                 .filter(c => c.creator.id == data.person_view.person.id)
817                 .forEach(c => (c.creator.banned = data.banned));
818               if (postRes.post_view.creator.id == data.person_view.person.id) {
819                 postRes.post_view.creator.banned = data.banned;
820               }
821               this.setState(this.state);
822             },
823             none: void 0,
824           }),
825         none: void 0,
826       });
827     } else if (op == UserOperation.AddAdmin) {
828       let data = wsJsonToRes<AddAdminResponse>(msg, AddAdminResponse);
829       this.state.siteRes.admins = data.admins;
830       this.setState(this.state);
831     } else if (op == UserOperation.Search) {
832       let data = wsJsonToRes<SearchResponse>(msg, SearchResponse);
833       let xPosts = data.posts.filter(
834         p => p.post.id != Number(this.props.match.params.id)
835       );
836       this.state.crossPosts = xPosts.length > 0 ? Some(xPosts) : None;
837       this.setState(this.state);
838     } else if (op == UserOperation.LeaveAdmin) {
839       let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse);
840       this.state.siteRes = data;
841       this.setState(this.state);
842     } else if (op == UserOperation.TransferCommunity) {
843       let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse);
844       this.state.postRes.match({
845         some: res => {
846           res.community_view = data.community_view;
847           res.post_view.community = data.community_view.community;
848           res.moderators = data.moderators;
849           this.setState(this.state);
850         },
851         none: void 0,
852       });
853     } else if (op == UserOperation.BlockPerson) {
854       let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
855       updatePersonBlock(data);
856     } else if (op == UserOperation.CreatePostReport) {
857       let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
858       if (data) {
859         toast(i18n.t("report_created"));
860       }
861     } else if (op == UserOperation.CreateCommentReport) {
862       let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
863       if (data) {
864         toast(i18n.t("report_created"));
865       }
866     } else if (
867       op == UserOperation.PurgePerson ||
868       op == UserOperation.PurgePost ||
869       op == UserOperation.PurgeComment ||
870       op == UserOperation.PurgeCommunity
871     ) {
872       let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse);
873       if (data.success) {
874         toast(i18n.t("purge_success"));
875         this.context.router.history.push(`/`);
876       }
877     }
878   }
879 }