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