]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/inbox.tsx
Fix I18 next circular reference
[lemmy-ui.git] / src / shared / components / person / inbox.tsx
1 import {
2   commentsToFlatNodes,
3   editCommentReply,
4   editMention,
5   editPrivateMessage,
6   editWith,
7   enableDownvotes,
8   getCommentParentId,
9   myAuth,
10   myAuthRequired,
11   setIsoData,
12   updatePersonBlock,
13 } from "@utils/app";
14 import { RouteDataResponse } from "@utils/types";
15 import { Component, linkEvent } from "inferno";
16 import {
17   AddAdmin,
18   AddModToCommunity,
19   BanFromCommunity,
20   BanFromCommunityResponse,
21   BanPerson,
22   BanPersonResponse,
23   BlockPerson,
24   CommentId,
25   CommentReplyResponse,
26   CommentReplyView,
27   CommentReportResponse,
28   CommentResponse,
29   CommentSortType,
30   CommentView,
31   CreateComment,
32   CreateCommentLike,
33   CreateCommentReport,
34   CreatePrivateMessage,
35   CreatePrivateMessageReport,
36   DeleteComment,
37   DeletePrivateMessage,
38   DistinguishComment,
39   EditComment,
40   EditPrivateMessage,
41   GetPersonMentionsResponse,
42   GetRepliesResponse,
43   GetSiteResponse,
44   MarkCommentReplyAsRead,
45   MarkPersonMentionAsRead,
46   MarkPrivateMessageAsRead,
47   PersonMentionResponse,
48   PersonMentionView,
49   PrivateMessageReportResponse,
50   PrivateMessageResponse,
51   PrivateMessageView,
52   PrivateMessagesResponse,
53   PurgeComment,
54   PurgeItemResponse,
55   PurgePerson,
56   PurgePost,
57   RemoveComment,
58   SaveComment,
59   TransferCommunity,
60 } from "lemmy-js-client";
61 import { fetchLimit, relTags } from "../../config";
62 import { CommentViewType, InitialFetchRequest } from "../../interfaces";
63 import { FirstLoadService, I18NextService, UserService } from "../../services";
64 import { HttpService, RequestState } from "../../services/HttpService";
65 import { toast } from "../../toast";
66 import { CommentNodes } from "../comment/comment-nodes";
67 import { CommentSortSelect } from "../common/comment-sort-select";
68 import { HtmlTags } from "../common/html-tags";
69 import { Icon, Spinner } from "../common/icon";
70 import { Paginator } from "../common/paginator";
71 import { PrivateMessage } from "../private_message/private-message";
72
73 enum UnreadOrAll {
74   Unread,
75   All,
76 }
77
78 enum MessageType {
79   All,
80   Replies,
81   Mentions,
82   Messages,
83 }
84
85 enum ReplyEnum {
86   Reply,
87   Mention,
88   Message,
89 }
90
91 type InboxData = RouteDataResponse<{
92   repliesRes: GetRepliesResponse;
93   mentionsRes: GetPersonMentionsResponse;
94   messagesRes: PrivateMessagesResponse;
95 }>;
96
97 type ReplyType = {
98   id: number;
99   type_: ReplyEnum;
100   view: CommentView | PrivateMessageView | PersonMentionView | CommentReplyView;
101   published: string;
102 };
103
104 interface InboxState {
105   unreadOrAll: UnreadOrAll;
106   messageType: MessageType;
107   repliesRes: RequestState<GetRepliesResponse>;
108   mentionsRes: RequestState<GetPersonMentionsResponse>;
109   messagesRes: RequestState<PrivateMessagesResponse>;
110   markAllAsReadRes: RequestState<GetRepliesResponse>;
111   sort: CommentSortType;
112   page: number;
113   siteRes: GetSiteResponse;
114   finished: Map<CommentId, boolean | undefined>;
115   isIsomorphic: boolean;
116 }
117
118 export class Inbox extends Component<any, InboxState> {
119   private isoData = setIsoData<InboxData>(this.context);
120   state: InboxState = {
121     unreadOrAll: UnreadOrAll.Unread,
122     messageType: MessageType.All,
123     sort: "New",
124     page: 1,
125     siteRes: this.isoData.site_res,
126     repliesRes: { state: "empty" },
127     mentionsRes: { state: "empty" },
128     messagesRes: { state: "empty" },
129     markAllAsReadRes: { state: "empty" },
130     finished: new Map(),
131     isIsomorphic: false,
132   };
133
134   constructor(props: any, context: any) {
135     super(props, context);
136
137     this.handleSortChange = this.handleSortChange.bind(this);
138     this.handlePageChange = this.handlePageChange.bind(this);
139
140     this.handleCreateComment = this.handleCreateComment.bind(this);
141     this.handleEditComment = this.handleEditComment.bind(this);
142     this.handleSaveComment = this.handleSaveComment.bind(this);
143     this.handleBlockPerson = this.handleBlockPerson.bind(this);
144     this.handleDeleteComment = this.handleDeleteComment.bind(this);
145     this.handleRemoveComment = this.handleRemoveComment.bind(this);
146     this.handleCommentVote = this.handleCommentVote.bind(this);
147     this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this);
148     this.handleAddAdmin = this.handleAddAdmin.bind(this);
149     this.handlePurgePerson = this.handlePurgePerson.bind(this);
150     this.handlePurgeComment = this.handlePurgeComment.bind(this);
151     this.handleCommentReport = this.handleCommentReport.bind(this);
152     this.handleDistinguishComment = this.handleDistinguishComment.bind(this);
153     this.handleTransferCommunity = this.handleTransferCommunity.bind(this);
154     this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this);
155     this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this);
156     this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this);
157     this.handleBanPerson = this.handleBanPerson.bind(this);
158
159     this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
160     this.handleMarkMessageAsRead = this.handleMarkMessageAsRead.bind(this);
161     this.handleMessageReport = this.handleMessageReport.bind(this);
162     this.handleCreateMessage = this.handleCreateMessage.bind(this);
163     this.handleEditMessage = this.handleEditMessage.bind(this);
164
165     // Only fetch the data if coming from another route
166     if (FirstLoadService.isFirstLoad) {
167       const { mentionsRes, messagesRes, repliesRes } = this.isoData.routeData;
168
169       this.state = {
170         ...this.state,
171         repliesRes,
172         mentionsRes,
173         messagesRes,
174         isIsomorphic: true,
175       };
176     }
177   }
178
179   async componentDidMount() {
180     if (!this.state.isIsomorphic) {
181       await this.refetch();
182     }
183   }
184
185   get documentTitle(): string {
186     const mui = UserService.Instance.myUserInfo;
187     return mui
188       ? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
189           "inbox"
190         )} - ${this.state.siteRes.site_view.site.name}`
191       : "";
192   }
193
194   get hasUnreads(): boolean {
195     if (this.state.unreadOrAll == UnreadOrAll.Unread) {
196       const { repliesRes, mentionsRes, messagesRes } = this.state;
197       const replyCount =
198         repliesRes.state == "success" ? repliesRes.data.replies.length : 0;
199       const mentionCount =
200         mentionsRes.state == "success" ? mentionsRes.data.mentions.length : 0;
201       const messageCount =
202         messagesRes.state == "success"
203           ? messagesRes.data.private_messages.length
204           : 0;
205
206       return replyCount + mentionCount + messageCount > 0;
207     } else {
208       return false;
209     }
210   }
211
212   render() {
213     const auth = myAuth();
214     const inboxRss = auth ? `/feeds/inbox/${auth}.xml` : undefined;
215     return (
216       <div className="inbox container-lg">
217         <div className="row">
218           <div className="col-12">
219             <HtmlTags
220               title={this.documentTitle}
221               path={this.context.router.route.match.url}
222             />
223             <h5 className="mb-2">
224               {I18NextService.i18n.t("inbox")}
225               {inboxRss && (
226                 <small>
227                   <a href={inboxRss} title="RSS" rel={relTags}>
228                     <Icon icon="rss" classes="ms-2 text-muted small" />
229                   </a>
230                   <link
231                     rel="alternate"
232                     type="application/atom+xml"
233                     href={inboxRss}
234                   />
235                 </small>
236               )}
237             </h5>
238             {this.hasUnreads && (
239               <button
240                 className="btn btn-secondary mb-2"
241                 onClick={linkEvent(this, this.handleMarkAllAsRead)}
242               >
243                 {this.state.markAllAsReadRes.state == "loading" ? (
244                   <Spinner />
245                 ) : (
246                   I18NextService.i18n.t("mark_all_as_read")
247                 )}
248               </button>
249             )}
250             {this.selects()}
251             {this.section}
252             <Paginator
253               page={this.state.page}
254               onChange={this.handlePageChange}
255             />
256           </div>
257         </div>
258       </div>
259     );
260   }
261
262   get section() {
263     switch (this.state.messageType) {
264       case MessageType.All: {
265         return this.all();
266       }
267       case MessageType.Replies: {
268         return this.replies();
269       }
270       case MessageType.Mentions: {
271         return this.mentions();
272       }
273       case MessageType.Messages: {
274         return this.messages();
275       }
276       default: {
277         return null;
278       }
279     }
280   }
281
282   unreadOrAllRadios() {
283     return (
284       <div className="btn-group btn-group-toggle flex-wrap mb-2">
285         <label
286           className={`btn btn-outline-secondary pointer
287             ${this.state.unreadOrAll == UnreadOrAll.Unread && "active"}
288           `}
289         >
290           <input
291             type="radio"
292             className="btn-check"
293             value={UnreadOrAll.Unread}
294             checked={this.state.unreadOrAll == UnreadOrAll.Unread}
295             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
296           />
297           {I18NextService.i18n.t("unread")}
298         </label>
299         <label
300           className={`btn btn-outline-secondary pointer
301             ${this.state.unreadOrAll == UnreadOrAll.All && "active"}
302           `}
303         >
304           <input
305             type="radio"
306             className="btn-check"
307             value={UnreadOrAll.All}
308             checked={this.state.unreadOrAll == UnreadOrAll.All}
309             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
310           />
311           {I18NextService.i18n.t("all")}
312         </label>
313       </div>
314     );
315   }
316
317   messageTypeRadios() {
318     return (
319       <div className="btn-group btn-group-toggle flex-wrap mb-2">
320         <label
321           className={`btn btn-outline-secondary pointer
322             ${this.state.messageType == MessageType.All && "active"}
323           `}
324         >
325           <input
326             type="radio"
327             className="btn-check"
328             value={MessageType.All}
329             checked={this.state.messageType == MessageType.All}
330             onChange={linkEvent(this, this.handleMessageTypeChange)}
331           />
332           {I18NextService.i18n.t("all")}
333         </label>
334         <label
335           className={`btn btn-outline-secondary pointer
336             ${this.state.messageType == MessageType.Replies && "active"}
337           `}
338         >
339           <input
340             type="radio"
341             className="btn-check"
342             value={MessageType.Replies}
343             checked={this.state.messageType == MessageType.Replies}
344             onChange={linkEvent(this, this.handleMessageTypeChange)}
345           />
346           {I18NextService.i18n.t("replies")}
347         </label>
348         <label
349           className={`btn btn-outline-secondary pointer
350             ${this.state.messageType == MessageType.Mentions && "active"}
351           `}
352         >
353           <input
354             type="radio"
355             className="btn-check"
356             value={MessageType.Mentions}
357             checked={this.state.messageType == MessageType.Mentions}
358             onChange={linkEvent(this, this.handleMessageTypeChange)}
359           />
360           {I18NextService.i18n.t("mentions")}
361         </label>
362         <label
363           className={`btn btn-outline-secondary pointer
364             ${this.state.messageType == MessageType.Messages && "active"}
365           `}
366         >
367           <input
368             type="radio"
369             className="btn-check"
370             value={MessageType.Messages}
371             checked={this.state.messageType == MessageType.Messages}
372             onChange={linkEvent(this, this.handleMessageTypeChange)}
373           />
374           {I18NextService.i18n.t("messages")}
375         </label>
376       </div>
377     );
378   }
379
380   selects() {
381     return (
382       <div className="mb-2">
383         <span className="me-3">{this.unreadOrAllRadios()}</span>
384         <span className="me-3">{this.messageTypeRadios()}</span>
385         <CommentSortSelect
386           sort={this.state.sort}
387           onChange={this.handleSortChange}
388         />
389       </div>
390     );
391   }
392
393   replyToReplyType(r: CommentReplyView): ReplyType {
394     return {
395       id: r.comment_reply.id,
396       type_: ReplyEnum.Reply,
397       view: r,
398       published: r.comment.published,
399     };
400   }
401
402   mentionToReplyType(r: PersonMentionView): ReplyType {
403     return {
404       id: r.person_mention.id,
405       type_: ReplyEnum.Mention,
406       view: r,
407       published: r.comment.published,
408     };
409   }
410
411   messageToReplyType(r: PrivateMessageView): ReplyType {
412     return {
413       id: r.private_message.id,
414       type_: ReplyEnum.Message,
415       view: r,
416       published: r.private_message.published,
417     };
418   }
419
420   buildCombined(): ReplyType[] {
421     const replies: ReplyType[] =
422       this.state.repliesRes.state == "success"
423         ? this.state.repliesRes.data.replies.map(this.replyToReplyType)
424         : [];
425     const mentions: ReplyType[] =
426       this.state.mentionsRes.state == "success"
427         ? this.state.mentionsRes.data.mentions.map(this.mentionToReplyType)
428         : [];
429     const messages: ReplyType[] =
430       this.state.messagesRes.state == "success"
431         ? this.state.messagesRes.data.private_messages.map(
432             this.messageToReplyType
433           )
434         : [];
435
436     return [...replies, ...mentions, ...messages].sort((a, b) =>
437       b.published.localeCompare(a.published)
438     );
439   }
440
441   renderReplyType(i: ReplyType) {
442     switch (i.type_) {
443       case ReplyEnum.Reply:
444         return (
445           <CommentNodes
446             key={i.id}
447             nodes={[
448               { comment_view: i.view as CommentView, children: [], depth: 0 },
449             ]}
450             viewType={CommentViewType.Flat}
451             finished={this.state.finished}
452             noIndent
453             markable
454             showCommunity
455             showContext
456             enableDownvotes={enableDownvotes(this.state.siteRes)}
457             allLanguages={this.state.siteRes.all_languages}
458             siteLanguages={this.state.siteRes.discussion_languages}
459             onSaveComment={this.handleSaveComment}
460             onBlockPerson={this.handleBlockPerson}
461             onDeleteComment={this.handleDeleteComment}
462             onRemoveComment={this.handleRemoveComment}
463             onCommentVote={this.handleCommentVote}
464             onCommentReport={this.handleCommentReport}
465             onDistinguishComment={this.handleDistinguishComment}
466             onAddModToCommunity={this.handleAddModToCommunity}
467             onAddAdmin={this.handleAddAdmin}
468             onTransferCommunity={this.handleTransferCommunity}
469             onPurgeComment={this.handlePurgeComment}
470             onPurgePerson={this.handlePurgePerson}
471             onCommentReplyRead={this.handleCommentReplyRead}
472             onPersonMentionRead={this.handlePersonMentionRead}
473             onBanPersonFromCommunity={this.handleBanFromCommunity}
474             onBanPerson={this.handleBanPerson}
475             onCreateComment={this.handleCreateComment}
476             onEditComment={this.handleEditComment}
477           />
478         );
479       case ReplyEnum.Mention:
480         return (
481           <CommentNodes
482             key={i.id}
483             nodes={[
484               {
485                 comment_view: i.view as PersonMentionView,
486                 children: [],
487                 depth: 0,
488               },
489             ]}
490             finished={this.state.finished}
491             viewType={CommentViewType.Flat}
492             noIndent
493             markable
494             showCommunity
495             showContext
496             enableDownvotes={enableDownvotes(this.state.siteRes)}
497             allLanguages={this.state.siteRes.all_languages}
498             siteLanguages={this.state.siteRes.discussion_languages}
499             onSaveComment={this.handleSaveComment}
500             onBlockPerson={this.handleBlockPerson}
501             onDeleteComment={this.handleDeleteComment}
502             onRemoveComment={this.handleRemoveComment}
503             onCommentVote={this.handleCommentVote}
504             onCommentReport={this.handleCommentReport}
505             onDistinguishComment={this.handleDistinguishComment}
506             onAddModToCommunity={this.handleAddModToCommunity}
507             onAddAdmin={this.handleAddAdmin}
508             onTransferCommunity={this.handleTransferCommunity}
509             onPurgeComment={this.handlePurgeComment}
510             onPurgePerson={this.handlePurgePerson}
511             onCommentReplyRead={this.handleCommentReplyRead}
512             onPersonMentionRead={this.handlePersonMentionRead}
513             onBanPersonFromCommunity={this.handleBanFromCommunity}
514             onBanPerson={this.handleBanPerson}
515             onCreateComment={this.handleCreateComment}
516             onEditComment={this.handleEditComment}
517           />
518         );
519       case ReplyEnum.Message:
520         return (
521           <PrivateMessage
522             key={i.id}
523             private_message_view={i.view as PrivateMessageView}
524             onDelete={this.handleDeleteMessage}
525             onMarkRead={this.handleMarkMessageAsRead}
526             onReport={this.handleMessageReport}
527             onCreate={this.handleCreateMessage}
528             onEdit={this.handleEditMessage}
529           />
530         );
531       default:
532         return <div />;
533     }
534   }
535
536   all() {
537     if (
538       this.state.repliesRes.state == "loading" ||
539       this.state.mentionsRes.state == "loading" ||
540       this.state.messagesRes.state == "loading"
541     ) {
542       return (
543         <h5>
544           <Spinner large />
545         </h5>
546       );
547     } else {
548       return (
549         <div>{this.buildCombined().map(r => this.renderReplyType(r))}</div>
550       );
551     }
552   }
553
554   replies() {
555     switch (this.state.repliesRes.state) {
556       case "loading":
557         return (
558           <h5>
559             <Spinner large />
560           </h5>
561         );
562       case "success": {
563         const replies = this.state.repliesRes.data.replies;
564         return (
565           <div>
566             <CommentNodes
567               nodes={commentsToFlatNodes(replies)}
568               viewType={CommentViewType.Flat}
569               finished={this.state.finished}
570               noIndent
571               markable
572               showCommunity
573               showContext
574               enableDownvotes={enableDownvotes(this.state.siteRes)}
575               allLanguages={this.state.siteRes.all_languages}
576               siteLanguages={this.state.siteRes.discussion_languages}
577               onSaveComment={this.handleSaveComment}
578               onBlockPerson={this.handleBlockPerson}
579               onDeleteComment={this.handleDeleteComment}
580               onRemoveComment={this.handleRemoveComment}
581               onCommentVote={this.handleCommentVote}
582               onCommentReport={this.handleCommentReport}
583               onDistinguishComment={this.handleDistinguishComment}
584               onAddModToCommunity={this.handleAddModToCommunity}
585               onAddAdmin={this.handleAddAdmin}
586               onTransferCommunity={this.handleTransferCommunity}
587               onPurgeComment={this.handlePurgeComment}
588               onPurgePerson={this.handlePurgePerson}
589               onCommentReplyRead={this.handleCommentReplyRead}
590               onPersonMentionRead={this.handlePersonMentionRead}
591               onBanPersonFromCommunity={this.handleBanFromCommunity}
592               onBanPerson={this.handleBanPerson}
593               onCreateComment={this.handleCreateComment}
594               onEditComment={this.handleEditComment}
595             />
596           </div>
597         );
598       }
599     }
600   }
601
602   mentions() {
603     switch (this.state.mentionsRes.state) {
604       case "loading":
605         return (
606           <h5>
607             <Spinner large />
608           </h5>
609         );
610       case "success": {
611         const mentions = this.state.mentionsRes.data.mentions;
612         return (
613           <div>
614             {mentions.map(umv => (
615               <CommentNodes
616                 key={umv.person_mention.id}
617                 nodes={[{ comment_view: umv, children: [], depth: 0 }]}
618                 viewType={CommentViewType.Flat}
619                 finished={this.state.finished}
620                 noIndent
621                 markable
622                 showCommunity
623                 showContext
624                 enableDownvotes={enableDownvotes(this.state.siteRes)}
625                 allLanguages={this.state.siteRes.all_languages}
626                 siteLanguages={this.state.siteRes.discussion_languages}
627                 onSaveComment={this.handleSaveComment}
628                 onBlockPerson={this.handleBlockPerson}
629                 onDeleteComment={this.handleDeleteComment}
630                 onRemoveComment={this.handleRemoveComment}
631                 onCommentVote={this.handleCommentVote}
632                 onCommentReport={this.handleCommentReport}
633                 onDistinguishComment={this.handleDistinguishComment}
634                 onAddModToCommunity={this.handleAddModToCommunity}
635                 onAddAdmin={this.handleAddAdmin}
636                 onTransferCommunity={this.handleTransferCommunity}
637                 onPurgeComment={this.handlePurgeComment}
638                 onPurgePerson={this.handlePurgePerson}
639                 onCommentReplyRead={this.handleCommentReplyRead}
640                 onPersonMentionRead={this.handlePersonMentionRead}
641                 onBanPersonFromCommunity={this.handleBanFromCommunity}
642                 onBanPerson={this.handleBanPerson}
643                 onCreateComment={this.handleCreateComment}
644                 onEditComment={this.handleEditComment}
645               />
646             ))}
647           </div>
648         );
649       }
650     }
651   }
652
653   messages() {
654     switch (this.state.messagesRes.state) {
655       case "loading":
656         return (
657           <h5>
658             <Spinner large />
659           </h5>
660         );
661       case "success": {
662         const messages = this.state.messagesRes.data.private_messages;
663         return (
664           <div>
665             {messages.map(pmv => (
666               <PrivateMessage
667                 key={pmv.private_message.id}
668                 private_message_view={pmv}
669                 onDelete={this.handleDeleteMessage}
670                 onMarkRead={this.handleMarkMessageAsRead}
671                 onReport={this.handleMessageReport}
672                 onCreate={this.handleCreateMessage}
673                 onEdit={this.handleEditMessage}
674               />
675             ))}
676           </div>
677         );
678       }
679     }
680   }
681
682   async handlePageChange(page: number) {
683     this.setState({ page });
684     await this.refetch();
685   }
686
687   async handleUnreadOrAllChange(i: Inbox, event: any) {
688     i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
689     await i.refetch();
690   }
691
692   async handleMessageTypeChange(i: Inbox, event: any) {
693     i.setState({ messageType: Number(event.target.value), page: 1 });
694     await i.refetch();
695   }
696
697   static async fetchInitialData({
698     client,
699     auth,
700   }: InitialFetchRequest): Promise<InboxData> {
701     const sort: CommentSortType = "New";
702
703     return {
704       mentionsRes: auth
705         ? await client.getPersonMentions({
706             sort,
707             unread_only: true,
708             page: 1,
709             limit: fetchLimit,
710             auth,
711           })
712         : { state: "empty" },
713       messagesRes: auth
714         ? await client.getPrivateMessages({
715             unread_only: true,
716             page: 1,
717             limit: fetchLimit,
718             auth,
719           })
720         : { state: "empty" },
721       repliesRes: auth
722         ? await client.getReplies({
723             sort,
724             unread_only: true,
725             page: 1,
726             limit: fetchLimit,
727             auth,
728           })
729         : { state: "empty" },
730     };
731   }
732
733   async refetch() {
734     const sort = this.state.sort;
735     const unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
736     const page = this.state.page;
737     const limit = fetchLimit;
738     const auth = myAuthRequired();
739
740     this.setState({ repliesRes: { state: "loading" } });
741     this.setState({
742       repliesRes: await HttpService.client.getReplies({
743         sort,
744         unread_only,
745         page,
746         limit,
747         auth,
748       }),
749     });
750
751     this.setState({ mentionsRes: { state: "loading" } });
752     this.setState({
753       mentionsRes: await HttpService.client.getPersonMentions({
754         sort,
755         unread_only,
756         page,
757         limit,
758         auth,
759       }),
760     });
761
762     this.setState({ messagesRes: { state: "loading" } });
763     this.setState({
764       messagesRes: await HttpService.client.getPrivateMessages({
765         unread_only,
766         page,
767         limit,
768         auth,
769       }),
770     });
771   }
772
773   async handleSortChange(val: CommentSortType) {
774     this.setState({ sort: val, page: 1 });
775     await this.refetch();
776   }
777
778   async handleMarkAllAsRead(i: Inbox) {
779     i.setState({ markAllAsReadRes: { state: "loading" } });
780
781     i.setState({
782       markAllAsReadRes: await HttpService.client.markAllAsRead({
783         auth: myAuthRequired(),
784       }),
785     });
786
787     if (i.state.markAllAsReadRes.state == "success") {
788       i.setState({
789         repliesRes: { state: "empty" },
790         mentionsRes: { state: "empty" },
791         messagesRes: { state: "empty" },
792       });
793     }
794   }
795
796   async handleAddModToCommunity(form: AddModToCommunity) {
797     // TODO not sure what to do here
798     HttpService.client.addModToCommunity(form);
799   }
800
801   async handlePurgePerson(form: PurgePerson) {
802     const purgePersonRes = await HttpService.client.purgePerson(form);
803     this.purgeItem(purgePersonRes);
804   }
805
806   async handlePurgeComment(form: PurgeComment) {
807     const purgeCommentRes = await HttpService.client.purgeComment(form);
808     this.purgeItem(purgeCommentRes);
809   }
810
811   async handlePurgePost(form: PurgePost) {
812     const purgeRes = await HttpService.client.purgePost(form);
813     this.purgeItem(purgeRes);
814   }
815
816   async handleBlockPerson(form: BlockPerson) {
817     const blockPersonRes = await HttpService.client.blockPerson(form);
818     if (blockPersonRes.state == "success") {
819       updatePersonBlock(blockPersonRes.data);
820     }
821   }
822
823   async handleCreateComment(form: CreateComment) {
824     const res = await HttpService.client.createComment(form);
825
826     if (res.state === "success") {
827       toast(I18NextService.i18n.t("reply_sent"));
828       this.findAndUpdateComment(res);
829     }
830
831     return res;
832   }
833
834   async handleEditComment(form: EditComment) {
835     const res = await HttpService.client.editComment(form);
836
837     if (res.state === "success") {
838       toast(I18NextService.i18n.t("edit"));
839       this.findAndUpdateComment(res);
840     } else if (res.state === "failed") {
841       toast(res.msg, "danger");
842     }
843
844     return res;
845   }
846
847   async handleDeleteComment(form: DeleteComment) {
848     const res = await HttpService.client.deleteComment(form);
849     if (res.state == "success") {
850       toast(I18NextService.i18n.t("deleted"));
851       this.findAndUpdateComment(res);
852     }
853   }
854
855   async handleRemoveComment(form: RemoveComment) {
856     const res = await HttpService.client.removeComment(form);
857     if (res.state == "success") {
858       toast(I18NextService.i18n.t("remove_comment"));
859       this.findAndUpdateComment(res);
860     }
861   }
862
863   async handleSaveComment(form: SaveComment) {
864     const res = await HttpService.client.saveComment(form);
865     this.findAndUpdateComment(res);
866   }
867
868   async handleCommentVote(form: CreateCommentLike) {
869     const res = await HttpService.client.likeComment(form);
870     this.findAndUpdateComment(res);
871   }
872
873   async handleCommentReport(form: CreateCommentReport) {
874     const reportRes = await HttpService.client.createCommentReport(form);
875     this.reportToast(reportRes);
876   }
877
878   async handleDistinguishComment(form: DistinguishComment) {
879     const res = await HttpService.client.distinguishComment(form);
880     this.findAndUpdateComment(res);
881   }
882
883   async handleAddAdmin(form: AddAdmin) {
884     const addAdminRes = await HttpService.client.addAdmin(form);
885
886     if (addAdminRes.state === "success") {
887       this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
888     }
889   }
890
891   async handleTransferCommunity(form: TransferCommunity) {
892     await HttpService.client.transferCommunity(form);
893     toast(I18NextService.i18n.t("transfer_community"));
894   }
895
896   async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
897     const res = await HttpService.client.markCommentReplyAsRead(form);
898     this.findAndUpdateCommentReply(res);
899   }
900
901   async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
902     const res = await HttpService.client.markPersonMentionAsRead(form);
903     this.findAndUpdateMention(res);
904   }
905
906   async handleBanFromCommunity(form: BanFromCommunity) {
907     const banRes = await HttpService.client.banFromCommunity(form);
908     this.updateBanFromCommunity(banRes);
909   }
910
911   async handleBanPerson(form: BanPerson) {
912     const banRes = await HttpService.client.banPerson(form);
913     this.updateBan(banRes);
914   }
915
916   async handleDeleteMessage(form: DeletePrivateMessage) {
917     const res = await HttpService.client.deletePrivateMessage(form);
918     this.findAndUpdateMessage(res);
919   }
920
921   async handleEditMessage(form: EditPrivateMessage) {
922     const res = await HttpService.client.editPrivateMessage(form);
923     this.findAndUpdateMessage(res);
924   }
925
926   async handleMarkMessageAsRead(form: MarkPrivateMessageAsRead) {
927     const res = await HttpService.client.markPrivateMessageAsRead(form);
928     this.findAndUpdateMessage(res);
929   }
930
931   async handleMessageReport(form: CreatePrivateMessageReport) {
932     const res = await HttpService.client.createPrivateMessageReport(form);
933     this.reportToast(res);
934   }
935
936   async handleCreateMessage(form: CreatePrivateMessage) {
937     const res = await HttpService.client.createPrivateMessage(form);
938     this.setState(s => {
939       if (s.messagesRes.state == "success" && res.state == "success") {
940         s.messagesRes.data.private_messages.unshift(
941           res.data.private_message_view
942         );
943       }
944
945       return s;
946     });
947   }
948
949   findAndUpdateMessage(res: RequestState<PrivateMessageResponse>) {
950     this.setState(s => {
951       if (s.messagesRes.state === "success" && res.state === "success") {
952         s.messagesRes.data.private_messages = editPrivateMessage(
953           res.data.private_message_view,
954           s.messagesRes.data.private_messages
955         );
956       }
957       return s;
958     });
959   }
960
961   updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
962     // Maybe not necessary
963     if (banRes.state == "success") {
964       this.setState(s => {
965         if (s.repliesRes.state == "success") {
966           s.repliesRes.data.replies
967             .filter(c => c.creator.id == banRes.data.person_view.person.id)
968             .forEach(
969               c => (c.creator_banned_from_community = banRes.data.banned)
970             );
971         }
972         if (s.mentionsRes.state == "success") {
973           s.mentionsRes.data.mentions
974             .filter(c => c.creator.id == banRes.data.person_view.person.id)
975             .forEach(
976               c => (c.creator_banned_from_community = banRes.data.banned)
977             );
978         }
979         return s;
980       });
981     }
982   }
983
984   updateBan(banRes: RequestState<BanPersonResponse>) {
985     // Maybe not necessary
986     if (banRes.state == "success") {
987       this.setState(s => {
988         if (s.repliesRes.state == "success") {
989           s.repliesRes.data.replies
990             .filter(c => c.creator.id == banRes.data.person_view.person.id)
991             .forEach(c => (c.creator.banned = banRes.data.banned));
992         }
993         if (s.mentionsRes.state == "success") {
994           s.mentionsRes.data.mentions
995             .filter(c => c.creator.id == banRes.data.person_view.person.id)
996             .forEach(c => (c.creator.banned = banRes.data.banned));
997         }
998         return s;
999       });
1000     }
1001   }
1002
1003   purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
1004     if (purgeRes.state == "success") {
1005       toast(I18NextService.i18n.t("purge_success"));
1006       this.context.router.history.push(`/`);
1007     }
1008   }
1009
1010   reportToast(
1011     res: RequestState<PrivateMessageReportResponse | CommentReportResponse>
1012   ) {
1013     if (res.state == "success") {
1014       toast(I18NextService.i18n.t("report_created"));
1015     }
1016   }
1017
1018   // A weird case, since you have only replies and mentions, not comment responses
1019   findAndUpdateComment(res: RequestState<CommentResponse>) {
1020     if (res.state == "success") {
1021       this.setState(s => {
1022         if (s.repliesRes.state == "success") {
1023           s.repliesRes.data.replies = editWith(
1024             res.data.comment_view,
1025             s.repliesRes.data.replies
1026           );
1027         }
1028         if (s.mentionsRes.state == "success") {
1029           s.mentionsRes.data.mentions = editWith(
1030             res.data.comment_view,
1031             s.mentionsRes.data.mentions
1032           );
1033         }
1034         // Set finished for the parent
1035         s.finished.set(
1036           getCommentParentId(res.data.comment_view.comment) ?? 0,
1037           true
1038         );
1039         return s;
1040       });
1041     }
1042   }
1043
1044   findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
1045     this.setState(s => {
1046       if (s.repliesRes.state == "success" && res.state == "success") {
1047         s.repliesRes.data.replies = editCommentReply(
1048           res.data.comment_reply_view,
1049           s.repliesRes.data.replies
1050         );
1051       }
1052       return s;
1053     });
1054   }
1055
1056   findAndUpdateMention(res: RequestState<PersonMentionResponse>) {
1057     this.setState(s => {
1058       if (s.mentionsRes.state == "success" && res.state == "success") {
1059         s.mentionsRes.data.mentions = editMention(
1060           res.data.person_mention_view,
1061           s.mentionsRes.data.mentions
1062         );
1063       }
1064       return s;
1065     });
1066   }
1067 }