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