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