]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/inbox.tsx
Merge branch 'main' into reduce-bundle
[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             markable
453             showCommunity
454             showContext
455             enableDownvotes={enableDownvotes(this.state.siteRes)}
456             allLanguages={this.state.siteRes.all_languages}
457             siteLanguages={this.state.siteRes.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       case ReplyEnum.Mention:
479         return (
480           <CommentNodes
481             key={i.id}
482             nodes={[
483               {
484                 comment_view: i.view as PersonMentionView,
485                 children: [],
486                 depth: 0,
487               },
488             ]}
489             finished={this.state.finished}
490             viewType={CommentViewType.Flat}
491             markable
492             showCommunity
493             showContext
494             enableDownvotes={enableDownvotes(this.state.siteRes)}
495             allLanguages={this.state.siteRes.all_languages}
496             siteLanguages={this.state.siteRes.discussion_languages}
497             onSaveComment={this.handleSaveComment}
498             onBlockPerson={this.handleBlockPerson}
499             onDeleteComment={this.handleDeleteComment}
500             onRemoveComment={this.handleRemoveComment}
501             onCommentVote={this.handleCommentVote}
502             onCommentReport={this.handleCommentReport}
503             onDistinguishComment={this.handleDistinguishComment}
504             onAddModToCommunity={this.handleAddModToCommunity}
505             onAddAdmin={this.handleAddAdmin}
506             onTransferCommunity={this.handleTransferCommunity}
507             onPurgeComment={this.handlePurgeComment}
508             onPurgePerson={this.handlePurgePerson}
509             onCommentReplyRead={this.handleCommentReplyRead}
510             onPersonMentionRead={this.handlePersonMentionRead}
511             onBanPersonFromCommunity={this.handleBanFromCommunity}
512             onBanPerson={this.handleBanPerson}
513             onCreateComment={this.handleCreateComment}
514             onEditComment={this.handleEditComment}
515           />
516         );
517       case ReplyEnum.Message:
518         return (
519           <PrivateMessage
520             key={i.id}
521             private_message_view={i.view as PrivateMessageView}
522             onDelete={this.handleDeleteMessage}
523             onMarkRead={this.handleMarkMessageAsRead}
524             onReport={this.handleMessageReport}
525             onCreate={this.handleCreateMessage}
526             onEdit={this.handleEditMessage}
527           />
528         );
529       default:
530         return <div />;
531     }
532   }
533
534   all() {
535     if (
536       this.state.repliesRes.state == "loading" ||
537       this.state.mentionsRes.state == "loading" ||
538       this.state.messagesRes.state == "loading"
539     ) {
540       return (
541         <h5>
542           <Spinner large />
543         </h5>
544       );
545     } else {
546       return (
547         <div>{this.buildCombined().map(r => this.renderReplyType(r))}</div>
548       );
549     }
550   }
551
552   replies() {
553     switch (this.state.repliesRes.state) {
554       case "loading":
555         return (
556           <h5>
557             <Spinner large />
558           </h5>
559         );
560       case "success": {
561         const replies = this.state.repliesRes.data.replies;
562         return (
563           <div>
564             <CommentNodes
565               nodes={commentsToFlatNodes(replies)}
566               viewType={CommentViewType.Flat}
567               finished={this.state.finished}
568               markable
569               showCommunity
570               showContext
571               enableDownvotes={enableDownvotes(this.state.siteRes)}
572               allLanguages={this.state.siteRes.all_languages}
573               siteLanguages={this.state.siteRes.discussion_languages}
574               onSaveComment={this.handleSaveComment}
575               onBlockPerson={this.handleBlockPerson}
576               onDeleteComment={this.handleDeleteComment}
577               onRemoveComment={this.handleRemoveComment}
578               onCommentVote={this.handleCommentVote}
579               onCommentReport={this.handleCommentReport}
580               onDistinguishComment={this.handleDistinguishComment}
581               onAddModToCommunity={this.handleAddModToCommunity}
582               onAddAdmin={this.handleAddAdmin}
583               onTransferCommunity={this.handleTransferCommunity}
584               onPurgeComment={this.handlePurgeComment}
585               onPurgePerson={this.handlePurgePerson}
586               onCommentReplyRead={this.handleCommentReplyRead}
587               onPersonMentionRead={this.handlePersonMentionRead}
588               onBanPersonFromCommunity={this.handleBanFromCommunity}
589               onBanPerson={this.handleBanPerson}
590               onCreateComment={this.handleCreateComment}
591               onEditComment={this.handleEditComment}
592             />
593           </div>
594         );
595       }
596     }
597   }
598
599   mentions() {
600     switch (this.state.mentionsRes.state) {
601       case "loading":
602         return (
603           <h5>
604             <Spinner large />
605           </h5>
606         );
607       case "success": {
608         const mentions = this.state.mentionsRes.data.mentions;
609         return (
610           <div>
611             {mentions.map(umv => (
612               <CommentNodes
613                 key={umv.person_mention.id}
614                 nodes={[{ comment_view: umv, children: [], depth: 0 }]}
615                 viewType={CommentViewType.Flat}
616                 finished={this.state.finished}
617                 markable
618                 showCommunity
619                 showContext
620                 enableDownvotes={enableDownvotes(this.state.siteRes)}
621                 allLanguages={this.state.siteRes.all_languages}
622                 siteLanguages={this.state.siteRes.discussion_languages}
623                 onSaveComment={this.handleSaveComment}
624                 onBlockPerson={this.handleBlockPerson}
625                 onDeleteComment={this.handleDeleteComment}
626                 onRemoveComment={this.handleRemoveComment}
627                 onCommentVote={this.handleCommentVote}
628                 onCommentReport={this.handleCommentReport}
629                 onDistinguishComment={this.handleDistinguishComment}
630                 onAddModToCommunity={this.handleAddModToCommunity}
631                 onAddAdmin={this.handleAddAdmin}
632                 onTransferCommunity={this.handleTransferCommunity}
633                 onPurgeComment={this.handlePurgeComment}
634                 onPurgePerson={this.handlePurgePerson}
635                 onCommentReplyRead={this.handleCommentReplyRead}
636                 onPersonMentionRead={this.handlePersonMentionRead}
637                 onBanPersonFromCommunity={this.handleBanFromCommunity}
638                 onBanPerson={this.handleBanPerson}
639                 onCreateComment={this.handleCreateComment}
640                 onEditComment={this.handleEditComment}
641               />
642             ))}
643           </div>
644         );
645       }
646     }
647   }
648
649   messages() {
650     switch (this.state.messagesRes.state) {
651       case "loading":
652         return (
653           <h5>
654             <Spinner large />
655           </h5>
656         );
657       case "success": {
658         const messages = this.state.messagesRes.data.private_messages;
659         return (
660           <div>
661             {messages.map(pmv => (
662               <PrivateMessage
663                 key={pmv.private_message.id}
664                 private_message_view={pmv}
665                 onDelete={this.handleDeleteMessage}
666                 onMarkRead={this.handleMarkMessageAsRead}
667                 onReport={this.handleMessageReport}
668                 onCreate={this.handleCreateMessage}
669                 onEdit={this.handleEditMessage}
670               />
671             ))}
672           </div>
673         );
674       }
675     }
676   }
677
678   async handlePageChange(page: number) {
679     this.setState({ page });
680     await this.refetch();
681   }
682
683   async handleUnreadOrAllChange(i: Inbox, event: any) {
684     i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
685     await i.refetch();
686   }
687
688   async handleMessageTypeChange(i: Inbox, event: any) {
689     i.setState({ messageType: Number(event.target.value), page: 1 });
690     await i.refetch();
691   }
692
693   static async fetchInitialData({
694     client,
695     auth,
696   }: InitialFetchRequest): Promise<InboxData> {
697     const sort: CommentSortType = "New";
698
699     return {
700       mentionsRes: auth
701         ? await client.getPersonMentions({
702             sort,
703             unread_only: true,
704             page: 1,
705             limit: fetchLimit,
706             auth,
707           })
708         : { state: "empty" },
709       messagesRes: auth
710         ? await client.getPrivateMessages({
711             unread_only: true,
712             page: 1,
713             limit: fetchLimit,
714             auth,
715           })
716         : { state: "empty" },
717       repliesRes: auth
718         ? await client.getReplies({
719             sort,
720             unread_only: true,
721             page: 1,
722             limit: fetchLimit,
723             auth,
724           })
725         : { state: "empty" },
726     };
727   }
728
729   async refetch() {
730     const sort = this.state.sort;
731     const unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
732     const page = this.state.page;
733     const limit = fetchLimit;
734     const auth = myAuthRequired();
735
736     this.setState({ repliesRes: { state: "loading" } });
737     this.setState({
738       repliesRes: await HttpService.client.getReplies({
739         sort,
740         unread_only,
741         page,
742         limit,
743         auth,
744       }),
745     });
746
747     this.setState({ mentionsRes: { state: "loading" } });
748     this.setState({
749       mentionsRes: await HttpService.client.getPersonMentions({
750         sort,
751         unread_only,
752         page,
753         limit,
754         auth,
755       }),
756     });
757
758     this.setState({ messagesRes: { state: "loading" } });
759     this.setState({
760       messagesRes: await HttpService.client.getPrivateMessages({
761         unread_only,
762         page,
763         limit,
764         auth,
765       }),
766     });
767   }
768
769   async handleSortChange(val: CommentSortType) {
770     this.setState({ sort: val, page: 1 });
771     await this.refetch();
772   }
773
774   async handleMarkAllAsRead(i: Inbox) {
775     i.setState({ markAllAsReadRes: { state: "loading" } });
776
777     i.setState({
778       markAllAsReadRes: await HttpService.client.markAllAsRead({
779         auth: myAuthRequired(),
780       }),
781     });
782
783     if (i.state.markAllAsReadRes.state == "success") {
784       i.setState({
785         repliesRes: { state: "empty" },
786         mentionsRes: { state: "empty" },
787         messagesRes: { state: "empty" },
788       });
789     }
790   }
791
792   async handleAddModToCommunity(form: AddModToCommunity) {
793     // TODO not sure what to do here
794     HttpService.client.addModToCommunity(form);
795   }
796
797   async handlePurgePerson(form: PurgePerson) {
798     const purgePersonRes = await HttpService.client.purgePerson(form);
799     this.purgeItem(purgePersonRes);
800   }
801
802   async handlePurgeComment(form: PurgeComment) {
803     const purgeCommentRes = await HttpService.client.purgeComment(form);
804     this.purgeItem(purgeCommentRes);
805   }
806
807   async handlePurgePost(form: PurgePost) {
808     const purgeRes = await HttpService.client.purgePost(form);
809     this.purgeItem(purgeRes);
810   }
811
812   async handleBlockPerson(form: BlockPerson) {
813     const blockPersonRes = await HttpService.client.blockPerson(form);
814     if (blockPersonRes.state == "success") {
815       updatePersonBlock(blockPersonRes.data);
816     }
817   }
818
819   async handleCreateComment(form: CreateComment) {
820     const res = await HttpService.client.createComment(form);
821
822     if (res.state === "success") {
823       toast(I18NextService.i18n.t("reply_sent"));
824       this.findAndUpdateComment(res);
825     }
826
827     return res;
828   }
829
830   async handleEditComment(form: EditComment) {
831     const res = await HttpService.client.editComment(form);
832
833     if (res.state === "success") {
834       toast(I18NextService.i18n.t("edit"));
835       this.findAndUpdateComment(res);
836     } else if (res.state === "failed") {
837       toast(res.msg, "danger");
838     }
839
840     return res;
841   }
842
843   async handleDeleteComment(form: DeleteComment) {
844     const res = await HttpService.client.deleteComment(form);
845     if (res.state == "success") {
846       toast(I18NextService.i18n.t("deleted"));
847       this.findAndUpdateComment(res);
848     }
849   }
850
851   async handleRemoveComment(form: RemoveComment) {
852     const res = await HttpService.client.removeComment(form);
853     if (res.state == "success") {
854       toast(I18NextService.i18n.t("remove_comment"));
855       this.findAndUpdateComment(res);
856     }
857   }
858
859   async handleSaveComment(form: SaveComment) {
860     const res = await HttpService.client.saveComment(form);
861     this.findAndUpdateComment(res);
862   }
863
864   async handleCommentVote(form: CreateCommentLike) {
865     const res = await HttpService.client.likeComment(form);
866     this.findAndUpdateComment(res);
867   }
868
869   async handleCommentReport(form: CreateCommentReport) {
870     const reportRes = await HttpService.client.createCommentReport(form);
871     this.reportToast(reportRes);
872   }
873
874   async handleDistinguishComment(form: DistinguishComment) {
875     const res = await HttpService.client.distinguishComment(form);
876     this.findAndUpdateComment(res);
877   }
878
879   async handleAddAdmin(form: AddAdmin) {
880     const addAdminRes = await HttpService.client.addAdmin(form);
881
882     if (addAdminRes.state === "success") {
883       this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
884     }
885   }
886
887   async handleTransferCommunity(form: TransferCommunity) {
888     await HttpService.client.transferCommunity(form);
889     toast(I18NextService.i18n.t("transfer_community"));
890   }
891
892   async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
893     const res = await HttpService.client.markCommentReplyAsRead(form);
894     this.findAndUpdateCommentReply(res);
895   }
896
897   async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
898     const res = await HttpService.client.markPersonMentionAsRead(form);
899     this.findAndUpdateMention(res);
900   }
901
902   async handleBanFromCommunity(form: BanFromCommunity) {
903     const banRes = await HttpService.client.banFromCommunity(form);
904     this.updateBanFromCommunity(banRes);
905   }
906
907   async handleBanPerson(form: BanPerson) {
908     const banRes = await HttpService.client.banPerson(form);
909     this.updateBan(banRes);
910   }
911
912   async handleDeleteMessage(form: DeletePrivateMessage) {
913     const res = await HttpService.client.deletePrivateMessage(form);
914     this.findAndUpdateMessage(res);
915   }
916
917   async handleEditMessage(form: EditPrivateMessage) {
918     const res = await HttpService.client.editPrivateMessage(form);
919     this.findAndUpdateMessage(res);
920   }
921
922   async handleMarkMessageAsRead(form: MarkPrivateMessageAsRead) {
923     const res = await HttpService.client.markPrivateMessageAsRead(form);
924     this.findAndUpdateMessage(res);
925   }
926
927   async handleMessageReport(form: CreatePrivateMessageReport) {
928     const res = await HttpService.client.createPrivateMessageReport(form);
929     this.reportToast(res);
930   }
931
932   async handleCreateMessage(form: CreatePrivateMessage) {
933     const res = await HttpService.client.createPrivateMessage(form);
934     this.setState(s => {
935       if (s.messagesRes.state == "success" && res.state == "success") {
936         s.messagesRes.data.private_messages.unshift(
937           res.data.private_message_view
938         );
939       }
940
941       return s;
942     });
943   }
944
945   findAndUpdateMessage(res: RequestState<PrivateMessageResponse>) {
946     this.setState(s => {
947       if (s.messagesRes.state === "success" && res.state === "success") {
948         s.messagesRes.data.private_messages = editPrivateMessage(
949           res.data.private_message_view,
950           s.messagesRes.data.private_messages
951         );
952       }
953       return s;
954     });
955   }
956
957   updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
958     // Maybe not necessary
959     if (banRes.state == "success") {
960       this.setState(s => {
961         if (s.repliesRes.state == "success") {
962           s.repliesRes.data.replies
963             .filter(c => c.creator.id == banRes.data.person_view.person.id)
964             .forEach(
965               c => (c.creator_banned_from_community = banRes.data.banned)
966             );
967         }
968         if (s.mentionsRes.state == "success") {
969           s.mentionsRes.data.mentions
970             .filter(c => c.creator.id == banRes.data.person_view.person.id)
971             .forEach(
972               c => (c.creator_banned_from_community = banRes.data.banned)
973             );
974         }
975         return s;
976       });
977     }
978   }
979
980   updateBan(banRes: RequestState<BanPersonResponse>) {
981     // Maybe not necessary
982     if (banRes.state == "success") {
983       this.setState(s => {
984         if (s.repliesRes.state == "success") {
985           s.repliesRes.data.replies
986             .filter(c => c.creator.id == banRes.data.person_view.person.id)
987             .forEach(c => (c.creator.banned = banRes.data.banned));
988         }
989         if (s.mentionsRes.state == "success") {
990           s.mentionsRes.data.mentions
991             .filter(c => c.creator.id == banRes.data.person_view.person.id)
992             .forEach(c => (c.creator.banned = banRes.data.banned));
993         }
994         return s;
995       });
996     }
997   }
998
999   purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
1000     if (purgeRes.state == "success") {
1001       toast(I18NextService.i18n.t("purge_success"));
1002       this.context.router.history.push(`/`);
1003     }
1004   }
1005
1006   reportToast(
1007     res: RequestState<PrivateMessageReportResponse | CommentReportResponse>
1008   ) {
1009     if (res.state == "success") {
1010       toast(I18NextService.i18n.t("report_created"));
1011     }
1012   }
1013
1014   // A weird case, since you have only replies and mentions, not comment responses
1015   findAndUpdateComment(res: RequestState<CommentResponse>) {
1016     if (res.state == "success") {
1017       this.setState(s => {
1018         if (s.repliesRes.state == "success") {
1019           s.repliesRes.data.replies = editWith(
1020             res.data.comment_view,
1021             s.repliesRes.data.replies
1022           );
1023         }
1024         if (s.mentionsRes.state == "success") {
1025           s.mentionsRes.data.mentions = editWith(
1026             res.data.comment_view,
1027             s.mentionsRes.data.mentions
1028           );
1029         }
1030         // Set finished for the parent
1031         s.finished.set(
1032           getCommentParentId(res.data.comment_view.comment) ?? 0,
1033           true
1034         );
1035         return s;
1036       });
1037     }
1038   }
1039
1040   findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
1041     this.setState(s => {
1042       if (s.repliesRes.state == "success" && res.state == "success") {
1043         s.repliesRes.data.replies = editCommentReply(
1044           res.data.comment_reply_view,
1045           s.repliesRes.data.replies
1046         );
1047       }
1048       return s;
1049     });
1050   }
1051
1052   findAndUpdateMention(res: RequestState<PersonMentionResponse>) {
1053     this.setState(s => {
1054       if (s.mentionsRes.state == "success" && res.state == "success") {
1055         s.mentionsRes.data.mentions = editMention(
1056           res.data.person_mention_view,
1057           s.mentionsRes.data.mentions
1058         );
1059       }
1060       return s;
1061     });
1062   }
1063 }