]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/community.tsx
Merge branch 'main' into breakout-role-utils
[lemmy-ui.git] / src / shared / components / community / community.tsx
1 import { getQueryParams, getQueryString } from "@utils/helpers";
2 import type { QueryParams } from "@utils/types";
3 import { Component, linkEvent } from "inferno";
4 import { RouteComponentProps } from "inferno-router/dist/Route";
5 import {
6   AddAdmin,
7   AddModToCommunity,
8   AddModToCommunityResponse,
9   BanFromCommunity,
10   BanFromCommunityResponse,
11   BanPerson,
12   BanPersonResponse,
13   BlockCommunity,
14   BlockPerson,
15   CommentId,
16   CommentReplyResponse,
17   CommentResponse,
18   CommunityResponse,
19   CreateComment,
20   CreateCommentLike,
21   CreateCommentReport,
22   CreatePostLike,
23   CreatePostReport,
24   DeleteComment,
25   DeleteCommunity,
26   DeletePost,
27   DistinguishComment,
28   EditComment,
29   EditCommunity,
30   EditPost,
31   FeaturePost,
32   FollowCommunity,
33   GetComments,
34   GetCommentsResponse,
35   GetCommunity,
36   GetCommunityResponse,
37   GetPosts,
38   GetPostsResponse,
39   GetSiteResponse,
40   LockPost,
41   MarkCommentReplyAsRead,
42   MarkPersonMentionAsRead,
43   PostResponse,
44   PurgeComment,
45   PurgeCommunity,
46   PurgeItemResponse,
47   PurgePerson,
48   PurgePost,
49   RemoveComment,
50   RemoveCommunity,
51   RemovePost,
52   SaveComment,
53   SavePost,
54   SortType,
55   TransferCommunity,
56 } from "lemmy-js-client";
57 import { i18n } from "../../i18next";
58 import {
59   CommentViewType,
60   DataType,
61   InitialFetchRequest,
62 } from "../../interfaces";
63 import { UserService } from "../../services";
64 import { FirstLoadService } from "../../services/FirstLoadService";
65 import { HttpService, RequestState } from "../../services/HttpService";
66 import {
67   RouteDataResponse,
68   commentsToFlatNodes,
69   communityRSSUrl,
70   editComment,
71   editPost,
72   editWith,
73   enableDownvotes,
74   enableNsfw,
75   fetchLimit,
76   getCommentParentId,
77   getDataTypeString,
78   getPageFromString,
79   myAuth,
80   postToCommentSortType,
81   relTags,
82   restoreScrollPosition,
83   saveScrollPosition,
84   setIsoData,
85   setupTippy,
86   showLocal,
87   toast,
88   updateCommunityBlock,
89   updatePersonBlock,
90 } from "../../utils";
91 import { CommentNodes } from "../comment/comment-nodes";
92 import { BannerIconHeader } from "../common/banner-icon-header";
93 import { DataTypeSelect } from "../common/data-type-select";
94 import { HtmlTags } from "../common/html-tags";
95 import { Icon, Spinner } from "../common/icon";
96 import { Paginator } from "../common/paginator";
97 import { SortSelect } from "../common/sort-select";
98 import { Sidebar } from "../community/sidebar";
99 import { SiteSidebar } from "../home/site-sidebar";
100 import { PostListings } from "../post/post-listings";
101 import { CommunityLink } from "./community-link";
102
103 type CommunityData = RouteDataResponse<{
104   communityRes: GetCommunityResponse;
105   postsRes: GetPostsResponse;
106   commentsRes: GetCommentsResponse;
107 }>;
108
109 interface State {
110   communityRes: RequestState<GetCommunityResponse>;
111   postsRes: RequestState<GetPostsResponse>;
112   commentsRes: RequestState<GetCommentsResponse>;
113   siteRes: GetSiteResponse;
114   showSidebarMobile: boolean;
115   finished: Map<CommentId, boolean | undefined>;
116   isIsomorphic: boolean;
117 }
118
119 interface CommunityProps {
120   dataType: DataType;
121   sort: SortType;
122   page: number;
123 }
124
125 function getCommunityQueryParams() {
126   return getQueryParams<CommunityProps>({
127     dataType: getDataTypeFromQuery,
128     page: getPageFromString,
129     sort: getSortTypeFromQuery,
130   });
131 }
132
133 function getDataTypeFromQuery(type?: string): DataType {
134   return type ? DataType[type] : DataType.Post;
135 }
136
137 function getSortTypeFromQuery(type?: string): SortType {
138   const mySortType =
139     UserService.Instance.myUserInfo?.local_user_view.local_user
140       .default_sort_type;
141
142   return type ? (type as SortType) : mySortType ?? "Active";
143 }
144
145 export class Community extends Component<
146   RouteComponentProps<{ name: string }>,
147   State
148 > {
149   private isoData = setIsoData<CommunityData>(this.context);
150   state: State = {
151     communityRes: { state: "empty" },
152     postsRes: { state: "empty" },
153     commentsRes: { state: "empty" },
154     siteRes: this.isoData.site_res,
155     showSidebarMobile: false,
156     finished: new Map(),
157     isIsomorphic: false,
158   };
159
160   constructor(props: RouteComponentProps<{ name: string }>, context: any) {
161     super(props, context);
162
163     this.handleSortChange = this.handleSortChange.bind(this);
164     this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
165     this.handlePageChange = this.handlePageChange.bind(this);
166
167     // All of the action binds
168     this.handleDeleteCommunity = this.handleDeleteCommunity.bind(this);
169     this.handleEditCommunity = this.handleEditCommunity.bind(this);
170     this.handleFollow = this.handleFollow.bind(this);
171     this.handleRemoveCommunity = this.handleRemoveCommunity.bind(this);
172     this.handleCreateComment = this.handleCreateComment.bind(this);
173     this.handleEditComment = this.handleEditComment.bind(this);
174     this.handleSaveComment = this.handleSaveComment.bind(this);
175     this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
176     this.handleBlockPerson = this.handleBlockPerson.bind(this);
177     this.handleDeleteComment = this.handleDeleteComment.bind(this);
178     this.handleRemoveComment = this.handleRemoveComment.bind(this);
179     this.handleCommentVote = this.handleCommentVote.bind(this);
180     this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this);
181     this.handleAddAdmin = this.handleAddAdmin.bind(this);
182     this.handlePurgePerson = this.handlePurgePerson.bind(this);
183     this.handlePurgeComment = this.handlePurgeComment.bind(this);
184     this.handleCommentReport = this.handleCommentReport.bind(this);
185     this.handleDistinguishComment = this.handleDistinguishComment.bind(this);
186     this.handleTransferCommunity = this.handleTransferCommunity.bind(this);
187     this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this);
188     this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this);
189     this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this);
190     this.handleBanPerson = this.handleBanPerson.bind(this);
191     this.handlePostVote = this.handlePostVote.bind(this);
192     this.handlePostEdit = this.handlePostEdit.bind(this);
193     this.handlePostReport = this.handlePostReport.bind(this);
194     this.handleLockPost = this.handleLockPost.bind(this);
195     this.handleDeletePost = this.handleDeletePost.bind(this);
196     this.handleRemovePost = this.handleRemovePost.bind(this);
197     this.handleSavePost = this.handleSavePost.bind(this);
198     this.handlePurgePost = this.handlePurgePost.bind(this);
199     this.handleFeaturePost = this.handleFeaturePost.bind(this);
200
201     // Only fetch the data if coming from another route
202     if (FirstLoadService.isFirstLoad) {
203       const { communityRes, commentsRes, postsRes } = this.isoData.routeData;
204
205       this.state = {
206         ...this.state,
207         isIsomorphic: true,
208         commentsRes,
209         communityRes,
210         postsRes,
211       };
212     }
213   }
214
215   async fetchCommunity() {
216     this.setState({ communityRes: { state: "loading" } });
217     this.setState({
218       communityRes: await HttpService.client.getCommunity({
219         name: this.props.match.params.name,
220         auth: myAuth(),
221       }),
222     });
223   }
224
225   async componentDidMount() {
226     if (!this.state.isIsomorphic) {
227       await Promise.all([this.fetchCommunity(), this.fetchData()]);
228     }
229
230     setupTippy();
231   }
232
233   componentWillUnmount() {
234     saveScrollPosition(this.context);
235   }
236
237   static async fetchInitialData({
238     client,
239     path,
240     query: { dataType: urlDataType, page: urlPage, sort: urlSort },
241     auth,
242   }: InitialFetchRequest<QueryParams<CommunityProps>>): Promise<
243     Promise<CommunityData>
244   > {
245     const pathSplit = path.split("/");
246
247     const communityName = pathSplit[2];
248     const communityForm: GetCommunity = {
249       name: communityName,
250       auth,
251     };
252
253     const dataType = getDataTypeFromQuery(urlDataType);
254
255     const sort = getSortTypeFromQuery(urlSort);
256
257     const page = getPageFromString(urlPage);
258
259     let postsResponse: RequestState<GetPostsResponse> = { state: "empty" };
260     let commentsResponse: RequestState<GetCommentsResponse> = {
261       state: "empty",
262     };
263
264     if (dataType === DataType.Post) {
265       const getPostsForm: GetPosts = {
266         community_name: communityName,
267         page,
268         limit: fetchLimit,
269         sort,
270         type_: "All",
271         saved_only: false,
272         auth,
273       };
274
275       postsResponse = await client.getPosts(getPostsForm);
276     } else {
277       const getCommentsForm: GetComments = {
278         community_name: communityName,
279         page,
280         limit: fetchLimit,
281         sort: postToCommentSortType(sort),
282         type_: "All",
283         saved_only: false,
284         auth,
285       };
286
287       commentsResponse = await client.getComments(getCommentsForm);
288     }
289
290     return {
291       communityRes: await client.getCommunity(communityForm),
292       commentsRes: commentsResponse,
293       postsRes: postsResponse,
294     };
295   }
296
297   get documentTitle(): string {
298     const cRes = this.state.communityRes;
299     return cRes.state == "success"
300       ? `${cRes.data.community_view.community.title} - ${this.isoData.site_res.site_view.site.name}`
301       : "";
302   }
303
304   renderCommunity() {
305     switch (this.state.communityRes.state) {
306       case "loading":
307         return (
308           <h5>
309             <Spinner large />
310           </h5>
311         );
312       case "success": {
313         const res = this.state.communityRes.data;
314         const { page } = getCommunityQueryParams();
315
316         return (
317           <>
318             <HtmlTags
319               title={this.documentTitle}
320               path={this.context.router.route.match.url}
321               description={res.community_view.community.description}
322               image={res.community_view.community.icon}
323             />
324
325             <div className="row">
326               <div className="col-12 col-md-8">
327                 {this.communityInfo(res)}
328                 <div className="d-block d-md-none">
329                   <button
330                     className="btn btn-secondary d-inline-block mb-2 me-3"
331                     onClick={linkEvent(this, this.handleShowSidebarMobile)}
332                   >
333                     {i18n.t("sidebar")}{" "}
334                     <Icon
335                       icon={
336                         this.state.showSidebarMobile
337                           ? `minus-square`
338                           : `plus-square`
339                       }
340                       classes="icon-inline"
341                     />
342                   </button>
343                   {this.state.showSidebarMobile && this.sidebar(res)}
344                 </div>
345                 {this.selects(res)}
346                 {this.listings(res)}
347                 <Paginator page={page} onChange={this.handlePageChange} />
348               </div>
349               <div className="d-none d-md-block col-md-4">
350                 {this.sidebar(res)}
351               </div>
352             </div>
353           </>
354         );
355       }
356     }
357   }
358
359   render() {
360     return <div className="container-lg">{this.renderCommunity()}</div>;
361   }
362
363   sidebar(res: GetCommunityResponse) {
364     const { site_res } = this.isoData;
365     // For some reason, this returns an empty vec if it matches the site langs
366     const communityLangs =
367       res.discussion_languages.length === 0
368         ? site_res.all_languages.map(({ id }) => id)
369         : res.discussion_languages;
370
371     return (
372       <>
373         <Sidebar
374           community_view={res.community_view}
375           moderators={res.moderators}
376           admins={site_res.admins}
377           enableNsfw={enableNsfw(site_res)}
378           editable
379           allLanguages={site_res.all_languages}
380           siteLanguages={site_res.discussion_languages}
381           communityLanguages={communityLangs}
382           onDeleteCommunity={this.handleDeleteCommunity}
383           onRemoveCommunity={this.handleRemoveCommunity}
384           onLeaveModTeam={this.handleAddModToCommunity}
385           onFollowCommunity={this.handleFollow}
386           onBlockCommunity={this.handleBlockCommunity}
387           onPurgeCommunity={this.handlePurgeCommunity}
388           onEditCommunity={this.handleEditCommunity}
389         />
390         {!res.community_view.community.local && res.site && (
391           <SiteSidebar site={res.site} showLocal={showLocal(this.isoData)} />
392         )}
393       </>
394     );
395   }
396
397   listings(communityRes: GetCommunityResponse) {
398     const { dataType } = getCommunityQueryParams();
399     const { site_res } = this.isoData;
400
401     if (dataType === DataType.Post) {
402       switch (this.state.postsRes.state) {
403         case "loading":
404           return (
405             <h5>
406               <Spinner large />
407             </h5>
408           );
409         case "success":
410           return (
411             <PostListings
412               posts={this.state.postsRes.data.posts}
413               removeDuplicates
414               enableDownvotes={enableDownvotes(site_res)}
415               enableNsfw={enableNsfw(site_res)}
416               allLanguages={site_res.all_languages}
417               siteLanguages={site_res.discussion_languages}
418               onBlockPerson={this.handleBlockPerson}
419               onPostEdit={this.handlePostEdit}
420               onPostVote={this.handlePostVote}
421               onPostReport={this.handlePostReport}
422               onLockPost={this.handleLockPost}
423               onDeletePost={this.handleDeletePost}
424               onRemovePost={this.handleRemovePost}
425               onSavePost={this.handleSavePost}
426               onPurgePerson={this.handlePurgePerson}
427               onPurgePost={this.handlePurgePost}
428               onBanPerson={this.handleBanPerson}
429               onBanPersonFromCommunity={this.handleBanFromCommunity}
430               onAddModToCommunity={this.handleAddModToCommunity}
431               onAddAdmin={this.handleAddAdmin}
432               onTransferCommunity={this.handleTransferCommunity}
433               onFeaturePost={this.handleFeaturePost}
434             />
435           );
436       }
437     } else {
438       switch (this.state.commentsRes.state) {
439         case "loading":
440           return (
441             <h5>
442               <Spinner large />
443             </h5>
444           );
445         case "success":
446           return (
447             <CommentNodes
448               nodes={commentsToFlatNodes(this.state.commentsRes.data.comments)}
449               viewType={CommentViewType.Flat}
450               finished={this.state.finished}
451               noIndent
452               showContext
453               enableDownvotes={enableDownvotes(site_res)}
454               moderators={communityRes.moderators}
455               admins={site_res.admins}
456               allLanguages={site_res.all_languages}
457               siteLanguages={site_res.discussion_languages}
458               onSaveComment={this.handleSaveComment}
459               onBlockPerson={this.handleBlockPerson}
460               onDeleteComment={this.handleDeleteComment}
461               onRemoveComment={this.handleRemoveComment}
462               onCommentVote={this.handleCommentVote}
463               onCommentReport={this.handleCommentReport}
464               onDistinguishComment={this.handleDistinguishComment}
465               onAddModToCommunity={this.handleAddModToCommunity}
466               onAddAdmin={this.handleAddAdmin}
467               onTransferCommunity={this.handleTransferCommunity}
468               onPurgeComment={this.handlePurgeComment}
469               onPurgePerson={this.handlePurgePerson}
470               onCommentReplyRead={this.handleCommentReplyRead}
471               onPersonMentionRead={this.handlePersonMentionRead}
472               onBanPersonFromCommunity={this.handleBanFromCommunity}
473               onBanPerson={this.handleBanPerson}
474               onCreateComment={this.handleCreateComment}
475               onEditComment={this.handleEditComment}
476             />
477           );
478       }
479     }
480   }
481
482   communityInfo(res: GetCommunityResponse) {
483     const community = res.community_view.community;
484
485     return (
486       community && (
487         <div className="mb-2">
488           <BannerIconHeader banner={community.banner} icon={community.icon} />
489           <h5 className="mb-0 overflow-wrap-anywhere">{community.title}</h5>
490           <CommunityLink
491             community={community}
492             realLink
493             useApubName
494             muted
495             hideAvatar
496           />
497         </div>
498       )
499     );
500   }
501
502   selects(res: GetCommunityResponse) {
503     // let communityRss = this.state.communityRes.map(r =>
504     //   communityRSSUrl(r.community_view.community.actor_id, this.state.sort)
505     // );
506     const { dataType, sort } = getCommunityQueryParams();
507     const communityRss = res
508       ? communityRSSUrl(res.community_view.community.actor_id, sort)
509       : undefined;
510
511     return (
512       <div className="mb-3">
513         <span className="me-3">
514           <DataTypeSelect
515             type_={dataType}
516             onChange={this.handleDataTypeChange}
517           />
518         </span>
519         <span className="me-2">
520           <SortSelect sort={sort} onChange={this.handleSortChange} />
521         </span>
522         {communityRss && (
523           <>
524             <a href={communityRss} title="RSS" rel={relTags}>
525               <Icon icon="rss" classes="text-muted small" />
526             </a>
527             <link
528               rel="alternate"
529               type="application/atom+xml"
530               href={communityRss}
531             />
532           </>
533         )}
534       </div>
535     );
536   }
537
538   handlePageChange(page: number) {
539     this.updateUrl({ page });
540     window.scrollTo(0, 0);
541   }
542
543   handleSortChange(sort: SortType) {
544     this.updateUrl({ sort, page: 1 });
545     window.scrollTo(0, 0);
546   }
547
548   handleDataTypeChange(dataType: DataType) {
549     this.updateUrl({ dataType, page: 1 });
550     window.scrollTo(0, 0);
551   }
552
553   handleShowSidebarMobile(i: Community) {
554     i.setState(({ showSidebarMobile }) => ({
555       showSidebarMobile: !showSidebarMobile,
556     }));
557   }
558
559   async updateUrl({ dataType, page, sort }: Partial<CommunityProps>) {
560     const {
561       dataType: urlDataType,
562       page: urlPage,
563       sort: urlSort,
564     } = getCommunityQueryParams();
565
566     const queryParams: QueryParams<CommunityProps> = {
567       dataType: getDataTypeString(dataType ?? urlDataType),
568       page: (page ?? urlPage).toString(),
569       sort: sort ?? urlSort,
570     };
571
572     this.props.history.push(
573       `/c/${this.props.match.params.name}${getQueryString(queryParams)}`
574     );
575
576     await this.fetchData();
577   }
578
579   async fetchData() {
580     const { dataType, page, sort } = getCommunityQueryParams();
581     const { name } = this.props.match.params;
582
583     if (dataType === DataType.Post) {
584       this.setState({ postsRes: { state: "loading" } });
585       this.setState({
586         postsRes: await HttpService.client.getPosts({
587           page,
588           limit: fetchLimit,
589           sort,
590           type_: "All",
591           community_name: name,
592           saved_only: false,
593           auth: myAuth(),
594         }),
595       });
596     } else {
597       this.setState({ commentsRes: { state: "loading" } });
598       this.setState({
599         commentsRes: await HttpService.client.getComments({
600           page,
601           limit: fetchLimit,
602           sort: postToCommentSortType(sort),
603           type_: "All",
604           community_name: name,
605           saved_only: false,
606           auth: myAuth(),
607         }),
608       });
609     }
610
611     restoreScrollPosition(this.context);
612     setupTippy();
613   }
614
615   async handleDeleteCommunity(form: DeleteCommunity) {
616     const deleteCommunityRes = await HttpService.client.deleteCommunity(form);
617     this.updateCommunity(deleteCommunityRes);
618   }
619
620   async handleAddModToCommunity(form: AddModToCommunity) {
621     const addModRes = await HttpService.client.addModToCommunity(form);
622     this.updateModerators(addModRes);
623   }
624
625   async handleFollow(form: FollowCommunity) {
626     const followCommunityRes = await HttpService.client.followCommunity(form);
627     this.updateCommunity(followCommunityRes);
628
629     // Update myUserInfo
630     if (followCommunityRes.state == "success") {
631       const communityId = followCommunityRes.data.community_view.community.id;
632       const mui = UserService.Instance.myUserInfo;
633       if (mui) {
634         mui.follows = mui.follows.filter(i => i.community.id != communityId);
635       }
636     }
637   }
638
639   async handlePurgeCommunity(form: PurgeCommunity) {
640     const purgeCommunityRes = await HttpService.client.purgeCommunity(form);
641     this.purgeItem(purgeCommunityRes);
642   }
643
644   async handlePurgePerson(form: PurgePerson) {
645     const purgePersonRes = await HttpService.client.purgePerson(form);
646     this.purgeItem(purgePersonRes);
647   }
648
649   async handlePurgeComment(form: PurgeComment) {
650     const purgeCommentRes = await HttpService.client.purgeComment(form);
651     this.purgeItem(purgeCommentRes);
652   }
653
654   async handlePurgePost(form: PurgePost) {
655     const purgeRes = await HttpService.client.purgePost(form);
656     this.purgeItem(purgeRes);
657   }
658
659   async handleBlockCommunity(form: BlockCommunity) {
660     const blockCommunityRes = await HttpService.client.blockCommunity(form);
661     if (blockCommunityRes.state == "success") {
662       updateCommunityBlock(blockCommunityRes.data);
663       this.setState(s => {
664         if (s.communityRes.state == "success") {
665           s.communityRes.data.community_view.blocked =
666             blockCommunityRes.data.blocked;
667         }
668       });
669     }
670   }
671
672   async handleBlockPerson(form: BlockPerson) {
673     const blockPersonRes = await HttpService.client.blockPerson(form);
674     if (blockPersonRes.state == "success") {
675       updatePersonBlock(blockPersonRes.data);
676     }
677   }
678
679   async handleRemoveCommunity(form: RemoveCommunity) {
680     const removeCommunityRes = await HttpService.client.removeCommunity(form);
681     this.updateCommunity(removeCommunityRes);
682   }
683
684   async handleEditCommunity(form: EditCommunity) {
685     const res = await HttpService.client.editCommunity(form);
686     this.updateCommunity(res);
687
688     return res;
689   }
690
691   async handleCreateComment(form: CreateComment) {
692     const createCommentRes = await HttpService.client.createComment(form);
693     this.createAndUpdateComments(createCommentRes);
694
695     return createCommentRes;
696   }
697
698   async handleEditComment(form: EditComment) {
699     const editCommentRes = await HttpService.client.editComment(form);
700     this.findAndUpdateComment(editCommentRes);
701
702     return editCommentRes;
703   }
704
705   async handleDeleteComment(form: DeleteComment) {
706     const deleteCommentRes = await HttpService.client.deleteComment(form);
707     this.findAndUpdateComment(deleteCommentRes);
708   }
709
710   async handleDeletePost(form: DeletePost) {
711     const deleteRes = await HttpService.client.deletePost(form);
712     this.findAndUpdatePost(deleteRes);
713   }
714
715   async handleRemovePost(form: RemovePost) {
716     const removeRes = await HttpService.client.removePost(form);
717     this.findAndUpdatePost(removeRes);
718   }
719
720   async handleRemoveComment(form: RemoveComment) {
721     const removeCommentRes = await HttpService.client.removeComment(form);
722     this.findAndUpdateComment(removeCommentRes);
723   }
724
725   async handleSaveComment(form: SaveComment) {
726     const saveCommentRes = await HttpService.client.saveComment(form);
727     this.findAndUpdateComment(saveCommentRes);
728   }
729
730   async handleSavePost(form: SavePost) {
731     const saveRes = await HttpService.client.savePost(form);
732     this.findAndUpdatePost(saveRes);
733   }
734
735   async handleFeaturePost(form: FeaturePost) {
736     const featureRes = await HttpService.client.featurePost(form);
737     this.findAndUpdatePost(featureRes);
738   }
739
740   async handleCommentVote(form: CreateCommentLike) {
741     const voteRes = await HttpService.client.likeComment(form);
742     this.findAndUpdateComment(voteRes);
743   }
744
745   async handlePostEdit(form: EditPost) {
746     const res = await HttpService.client.editPost(form);
747     this.findAndUpdatePost(res);
748   }
749
750   async handlePostVote(form: CreatePostLike) {
751     const voteRes = await HttpService.client.likePost(form);
752     this.findAndUpdatePost(voteRes);
753   }
754
755   async handleCommentReport(form: CreateCommentReport) {
756     const reportRes = await HttpService.client.createCommentReport(form);
757     if (reportRes.state == "success") {
758       toast(i18n.t("report_created"));
759     }
760   }
761
762   async handlePostReport(form: CreatePostReport) {
763     const reportRes = await HttpService.client.createPostReport(form);
764     if (reportRes.state == "success") {
765       toast(i18n.t("report_created"));
766     }
767   }
768
769   async handleLockPost(form: LockPost) {
770     const lockRes = await HttpService.client.lockPost(form);
771     this.findAndUpdatePost(lockRes);
772   }
773
774   async handleDistinguishComment(form: DistinguishComment) {
775     const distinguishRes = await HttpService.client.distinguishComment(form);
776     this.findAndUpdateComment(distinguishRes);
777   }
778
779   async handleAddAdmin(form: AddAdmin) {
780     const addAdminRes = await HttpService.client.addAdmin(form);
781
782     if (addAdminRes.state == "success") {
783       this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
784     }
785   }
786
787   async handleTransferCommunity(form: TransferCommunity) {
788     const transferCommunityRes = await HttpService.client.transferCommunity(
789       form
790     );
791     toast(i18n.t("transfer_community"));
792     this.updateCommunityFull(transferCommunityRes);
793   }
794
795   async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
796     const readRes = await HttpService.client.markCommentReplyAsRead(form);
797     this.findAndUpdateCommentReply(readRes);
798   }
799
800   async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
801     // TODO not sure what to do here. Maybe it is actually optional, because post doesn't need it.
802     await HttpService.client.markPersonMentionAsRead(form);
803   }
804
805   async handleBanFromCommunity(form: BanFromCommunity) {
806     const banRes = await HttpService.client.banFromCommunity(form);
807     this.updateBanFromCommunity(banRes);
808   }
809
810   async handleBanPerson(form: BanPerson) {
811     const banRes = await HttpService.client.banPerson(form);
812     this.updateBan(banRes);
813   }
814
815   updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
816     // Maybe not necessary
817     if (banRes.state == "success") {
818       this.setState(s => {
819         if (s.postsRes.state == "success") {
820           s.postsRes.data.posts
821             .filter(c => c.creator.id == banRes.data.person_view.person.id)
822             .forEach(
823               c => (c.creator_banned_from_community = banRes.data.banned)
824             );
825         }
826         if (s.commentsRes.state == "success") {
827           s.commentsRes.data.comments
828             .filter(c => c.creator.id == banRes.data.person_view.person.id)
829             .forEach(
830               c => (c.creator_banned_from_community = banRes.data.banned)
831             );
832         }
833         return s;
834       });
835     }
836   }
837
838   updateBan(banRes: RequestState<BanPersonResponse>) {
839     // Maybe not necessary
840     if (banRes.state == "success") {
841       this.setState(s => {
842         if (s.postsRes.state == "success") {
843           s.postsRes.data.posts
844             .filter(c => c.creator.id == banRes.data.person_view.person.id)
845             .forEach(c => (c.creator.banned = banRes.data.banned));
846         }
847         if (s.commentsRes.state == "success") {
848           s.commentsRes.data.comments
849             .filter(c => c.creator.id == banRes.data.person_view.person.id)
850             .forEach(c => (c.creator.banned = banRes.data.banned));
851         }
852         return s;
853       });
854     }
855   }
856
857   updateCommunity(res: RequestState<CommunityResponse>) {
858     this.setState(s => {
859       if (s.communityRes.state == "success" && res.state == "success") {
860         s.communityRes.data.community_view = res.data.community_view;
861         s.communityRes.data.discussion_languages =
862           res.data.discussion_languages;
863       }
864       return s;
865     });
866   }
867
868   updateCommunityFull(res: RequestState<GetCommunityResponse>) {
869     this.setState(s => {
870       if (s.communityRes.state == "success" && res.state == "success") {
871         s.communityRes.data.community_view = res.data.community_view;
872         s.communityRes.data.moderators = res.data.moderators;
873       }
874       return s;
875     });
876   }
877
878   purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
879     if (purgeRes.state == "success") {
880       toast(i18n.t("purge_success"));
881       this.context.router.history.push(`/`);
882     }
883   }
884
885   findAndUpdateComment(res: RequestState<CommentResponse>) {
886     this.setState(s => {
887       if (s.commentsRes.state == "success" && res.state == "success") {
888         s.commentsRes.data.comments = editComment(
889           res.data.comment_view,
890           s.commentsRes.data.comments
891         );
892         s.finished.set(res.data.comment_view.comment.id, true);
893       }
894       return s;
895     });
896   }
897
898   createAndUpdateComments(res: RequestState<CommentResponse>) {
899     this.setState(s => {
900       if (s.commentsRes.state == "success" && res.state == "success") {
901         s.commentsRes.data.comments.unshift(res.data.comment_view);
902
903         // Set finished for the parent
904         s.finished.set(
905           getCommentParentId(res.data.comment_view.comment) ?? 0,
906           true
907         );
908       }
909       return s;
910     });
911   }
912
913   findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
914     this.setState(s => {
915       if (s.commentsRes.state == "success" && res.state == "success") {
916         s.commentsRes.data.comments = editWith(
917           res.data.comment_reply_view,
918           s.commentsRes.data.comments
919         );
920       }
921       return s;
922     });
923   }
924
925   findAndUpdatePost(res: RequestState<PostResponse>) {
926     this.setState(s => {
927       if (s.postsRes.state == "success" && res.state == "success") {
928         s.postsRes.data.posts = editPost(
929           res.data.post_view,
930           s.postsRes.data.posts
931         );
932       }
933       return s;
934     });
935   }
936
937   updateModerators(res: RequestState<AddModToCommunityResponse>) {
938     // Update the moderators
939     this.setState(s => {
940       if (s.communityRes.state == "success" && res.state == "success") {
941         s.communityRes.data.moderators = res.data.moderators;
942       }
943       return s;
944     });
945   }
946 }