]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/inbox.tsx
91bbee03eea14a241285b8eb69338552bc63a65c
[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 { i18n } from "../../i18next";
63 import { CommentViewType, InitialFetchRequest } from "../../interfaces";
64 import { UserService } from "../../services";
65 import { FirstLoadService } from "../../services/FirstLoadService";
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} ${i18n.t("inbox")} - ${
191           this.state.siteRes.site_view.site.name
192         }`
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             <h5 className="mb-2">
226               {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             </h5>
240             {this.hasUnreads && (
241               <button
242                 className="btn btn-secondary mb-2"
243                 onClick={linkEvent(this, this.handleMarkAllAsRead)}
244               >
245                 {this.state.markAllAsReadRes.state == "loading" ? (
246                   <Spinner />
247                 ) : (
248                   i18n.t("mark_all_as_read")
249                 )}
250               </button>
251             )}
252             {this.selects()}
253             {this.section}
254             <Paginator
255               page={this.state.page}
256               onChange={this.handlePageChange}
257             />
258           </div>
259         </div>
260       </div>
261     );
262   }
263
264   get section() {
265     switch (this.state.messageType) {
266       case MessageType.All: {
267         return this.all();
268       }
269       case MessageType.Replies: {
270         return this.replies();
271       }
272       case MessageType.Mentions: {
273         return this.mentions();
274       }
275       case MessageType.Messages: {
276         return this.messages();
277       }
278       default: {
279         return null;
280       }
281     }
282   }
283
284   unreadOrAllRadios() {
285     return (
286       <div className="btn-group btn-group-toggle flex-wrap mb-2">
287         <label
288           className={`btn btn-outline-secondary pointer
289             ${this.state.unreadOrAll == UnreadOrAll.Unread && "active"}
290           `}
291         >
292           <input
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           {i18n.t("unread")}
300         </label>
301         <label
302           className={`btn btn-outline-secondary pointer
303             ${this.state.unreadOrAll == UnreadOrAll.All && "active"}
304           `}
305         >
306           <input
307             type="radio"
308             className="btn-check"
309             value={UnreadOrAll.All}
310             checked={this.state.unreadOrAll == UnreadOrAll.All}
311             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
312           />
313           {i18n.t("all")}
314         </label>
315       </div>
316     );
317   }
318
319   messageTypeRadios() {
320     return (
321       <div className="btn-group btn-group-toggle flex-wrap mb-2">
322         <label
323           className={`btn btn-outline-secondary pointer
324             ${this.state.messageType == MessageType.All && "active"}
325           `}
326         >
327           <input
328             type="radio"
329             className="btn-check"
330             value={MessageType.All}
331             checked={this.state.messageType == MessageType.All}
332             onChange={linkEvent(this, this.handleMessageTypeChange)}
333           />
334           {i18n.t("all")}
335         </label>
336         <label
337           className={`btn btn-outline-secondary pointer
338             ${this.state.messageType == MessageType.Replies && "active"}
339           `}
340         >
341           <input
342             type="radio"
343             className="btn-check"
344             value={MessageType.Replies}
345             checked={this.state.messageType == MessageType.Replies}
346             onChange={linkEvent(this, this.handleMessageTypeChange)}
347           />
348           {i18n.t("replies")}
349         </label>
350         <label
351           className={`btn btn-outline-secondary pointer
352             ${this.state.messageType == MessageType.Mentions && "active"}
353           `}
354         >
355           <input
356             type="radio"
357             className="btn-check"
358             value={MessageType.Mentions}
359             checked={this.state.messageType == MessageType.Mentions}
360             onChange={linkEvent(this, this.handleMessageTypeChange)}
361           />
362           {i18n.t("mentions")}
363         </label>
364         <label
365           className={`btn btn-outline-secondary pointer
366             ${this.state.messageType == MessageType.Messages && "active"}
367           `}
368         >
369           <input
370             type="radio"
371             className="btn-check"
372             value={MessageType.Messages}
373             checked={this.state.messageType == MessageType.Messages}
374             onChange={linkEvent(this, this.handleMessageTypeChange)}
375           />
376           {i18n.t("messages")}
377         </label>
378       </div>
379     );
380   }
381
382   selects() {
383     return (
384       <div className="mb-2">
385         <span className="me-3">{this.unreadOrAllRadios()}</span>
386         <span className="me-3">{this.messageTypeRadios()}</span>
387         <CommentSortSelect
388           sort={this.state.sort}
389           onChange={this.handleSortChange}
390         />
391       </div>
392     );
393   }
394
395   replyToReplyType(r: CommentReplyView): ReplyType {
396     return {
397       id: r.comment_reply.id,
398       type_: ReplyEnum.Reply,
399       view: r,
400       published: r.comment.published,
401     };
402   }
403
404   mentionToReplyType(r: PersonMentionView): ReplyType {
405     return {
406       id: r.person_mention.id,
407       type_: ReplyEnum.Mention,
408       view: r,
409       published: r.comment.published,
410     };
411   }
412
413   messageToReplyType(r: PrivateMessageView): ReplyType {
414     return {
415       id: r.private_message.id,
416       type_: ReplyEnum.Message,
417       view: r,
418       published: r.private_message.published,
419     };
420   }
421
422   buildCombined(): ReplyType[] {
423     const replies: ReplyType[] =
424       this.state.repliesRes.state == "success"
425         ? this.state.repliesRes.data.replies.map(this.replyToReplyType)
426         : [];
427     const mentions: ReplyType[] =
428       this.state.mentionsRes.state == "success"
429         ? this.state.mentionsRes.data.mentions.map(this.mentionToReplyType)
430         : [];
431     const messages: ReplyType[] =
432       this.state.messagesRes.state == "success"
433         ? this.state.messagesRes.data.private_messages.map(
434             this.messageToReplyType
435           )
436         : [];
437
438     return [...replies, ...mentions, ...messages].sort((a, b) =>
439       b.published.localeCompare(a.published)
440     );
441   }
442
443   renderReplyType(i: ReplyType) {
444     switch (i.type_) {
445       case ReplyEnum.Reply:
446         return (
447           <CommentNodes
448             key={i.id}
449             nodes={[
450               { comment_view: i.view as CommentView, children: [], depth: 0 },
451             ]}
452             viewType={CommentViewType.Flat}
453             finished={this.state.finished}
454             noIndent
455             markable
456             showCommunity
457             showContext
458             enableDownvotes={enableDownvotes(this.state.siteRes)}
459             allLanguages={this.state.siteRes.all_languages}
460             siteLanguages={this.state.siteRes.discussion_languages}
461             onSaveComment={this.handleSaveComment}
462             onBlockPerson={this.handleBlockPerson}
463             onDeleteComment={this.handleDeleteComment}
464             onRemoveComment={this.handleRemoveComment}
465             onCommentVote={this.handleCommentVote}
466             onCommentReport={this.handleCommentReport}
467             onDistinguishComment={this.handleDistinguishComment}
468             onAddModToCommunity={this.handleAddModToCommunity}
469             onAddAdmin={this.handleAddAdmin}
470             onTransferCommunity={this.handleTransferCommunity}
471             onPurgeComment={this.handlePurgeComment}
472             onPurgePerson={this.handlePurgePerson}
473             onCommentReplyRead={this.handleCommentReplyRead}
474             onPersonMentionRead={this.handlePersonMentionRead}
475             onBanPersonFromCommunity={this.handleBanFromCommunity}
476             onBanPerson={this.handleBanPerson}
477             onCreateComment={this.handleCreateComment}
478             onEditComment={this.handleEditComment}
479           />
480         );
481       case ReplyEnum.Mention:
482         return (
483           <CommentNodes
484             key={i.id}
485             nodes={[
486               {
487                 comment_view: i.view as PersonMentionView,
488                 children: [],
489                 depth: 0,
490               },
491             ]}
492             finished={this.state.finished}
493             viewType={CommentViewType.Flat}
494             noIndent
495             markable
496             showCommunity
497             showContext
498             enableDownvotes={enableDownvotes(this.state.siteRes)}
499             allLanguages={this.state.siteRes.all_languages}
500             siteLanguages={this.state.siteRes.discussion_languages}
501             onSaveComment={this.handleSaveComment}
502             onBlockPerson={this.handleBlockPerson}
503             onDeleteComment={this.handleDeleteComment}
504             onRemoveComment={this.handleRemoveComment}
505             onCommentVote={this.handleCommentVote}
506             onCommentReport={this.handleCommentReport}
507             onDistinguishComment={this.handleDistinguishComment}
508             onAddModToCommunity={this.handleAddModToCommunity}
509             onAddAdmin={this.handleAddAdmin}
510             onTransferCommunity={this.handleTransferCommunity}
511             onPurgeComment={this.handlePurgeComment}
512             onPurgePerson={this.handlePurgePerson}
513             onCommentReplyRead={this.handleCommentReplyRead}
514             onPersonMentionRead={this.handlePersonMentionRead}
515             onBanPersonFromCommunity={this.handleBanFromCommunity}
516             onBanPerson={this.handleBanPerson}
517             onCreateComment={this.handleCreateComment}
518             onEditComment={this.handleEditComment}
519           />
520         );
521       case ReplyEnum.Message:
522         return (
523           <PrivateMessage
524             key={i.id}
525             private_message_view={i.view as PrivateMessageView}
526             onDelete={this.handleDeleteMessage}
527             onMarkRead={this.handleMarkMessageAsRead}
528             onReport={this.handleMessageReport}
529             onCreate={this.handleCreateMessage}
530             onEdit={this.handleEditMessage}
531           />
532         );
533       default:
534         return <div />;
535     }
536   }
537
538   all() {
539     if (
540       this.state.repliesRes.state == "loading" ||
541       this.state.mentionsRes.state == "loading" ||
542       this.state.messagesRes.state == "loading"
543     ) {
544       return (
545         <h5>
546           <Spinner large />
547         </h5>
548       );
549     } else {
550       return (
551         <div>{this.buildCombined().map(r => this.renderReplyType(r))}</div>
552       );
553     }
554   }
555
556   replies() {
557     switch (this.state.repliesRes.state) {
558       case "loading":
559         return (
560           <h5>
561             <Spinner large />
562           </h5>
563         );
564       case "success": {
565         const replies = this.state.repliesRes.data.replies;
566         return (
567           <div>
568             <CommentNodes
569               nodes={commentsToFlatNodes(replies)}
570               viewType={CommentViewType.Flat}
571               finished={this.state.finished}
572               noIndent
573               markable
574               showCommunity
575               showContext
576               enableDownvotes={enableDownvotes(this.state.siteRes)}
577               allLanguages={this.state.siteRes.all_languages}
578               siteLanguages={this.state.siteRes.discussion_languages}
579               onSaveComment={this.handleSaveComment}
580               onBlockPerson={this.handleBlockPerson}
581               onDeleteComment={this.handleDeleteComment}
582               onRemoveComment={this.handleRemoveComment}
583               onCommentVote={this.handleCommentVote}
584               onCommentReport={this.handleCommentReport}
585               onDistinguishComment={this.handleDistinguishComment}
586               onAddModToCommunity={this.handleAddModToCommunity}
587               onAddAdmin={this.handleAddAdmin}
588               onTransferCommunity={this.handleTransferCommunity}
589               onPurgeComment={this.handlePurgeComment}
590               onPurgePerson={this.handlePurgePerson}
591               onCommentReplyRead={this.handleCommentReplyRead}
592               onPersonMentionRead={this.handlePersonMentionRead}
593               onBanPersonFromCommunity={this.handleBanFromCommunity}
594               onBanPerson={this.handleBanPerson}
595               onCreateComment={this.handleCreateComment}
596               onEditComment={this.handleEditComment}
597             />
598           </div>
599         );
600       }
601     }
602   }
603
604   mentions() {
605     switch (this.state.mentionsRes.state) {
606       case "loading":
607         return (
608           <h5>
609             <Spinner large />
610           </h5>
611         );
612       case "success": {
613         const mentions = this.state.mentionsRes.data.mentions;
614         return (
615           <div>
616             {mentions.map(umv => (
617               <CommentNodes
618                 key={umv.person_mention.id}
619                 nodes={[{ comment_view: umv, children: [], depth: 0 }]}
620                 viewType={CommentViewType.Flat}
621                 finished={this.state.finished}
622                 noIndent
623                 markable
624                 showCommunity
625                 showContext
626                 enableDownvotes={enableDownvotes(this.state.siteRes)}
627                 allLanguages={this.state.siteRes.all_languages}
628                 siteLanguages={this.state.siteRes.discussion_languages}
629                 onSaveComment={this.handleSaveComment}
630                 onBlockPerson={this.handleBlockPerson}
631                 onDeleteComment={this.handleDeleteComment}
632                 onRemoveComment={this.handleRemoveComment}
633                 onCommentVote={this.handleCommentVote}
634                 onCommentReport={this.handleCommentReport}
635                 onDistinguishComment={this.handleDistinguishComment}
636                 onAddModToCommunity={this.handleAddModToCommunity}
637                 onAddAdmin={this.handleAddAdmin}
638                 onTransferCommunity={this.handleTransferCommunity}
639                 onPurgeComment={this.handlePurgeComment}
640                 onPurgePerson={this.handlePurgePerson}
641                 onCommentReplyRead={this.handleCommentReplyRead}
642                 onPersonMentionRead={this.handlePersonMentionRead}
643                 onBanPersonFromCommunity={this.handleBanFromCommunity}
644                 onBanPerson={this.handleBanPerson}
645                 onCreateComment={this.handleCreateComment}
646                 onEditComment={this.handleEditComment}
647               />
648             ))}
649           </div>
650         );
651       }
652     }
653   }
654
655   messages() {
656     switch (this.state.messagesRes.state) {
657       case "loading":
658         return (
659           <h5>
660             <Spinner large />
661           </h5>
662         );
663       case "success": {
664         const messages = this.state.messagesRes.data.private_messages;
665         return (
666           <div>
667             {messages.map(pmv => (
668               <PrivateMessage
669                 key={pmv.private_message.id}
670                 private_message_view={pmv}
671                 onDelete={this.handleDeleteMessage}
672                 onMarkRead={this.handleMarkMessageAsRead}
673                 onReport={this.handleMessageReport}
674                 onCreate={this.handleCreateMessage}
675                 onEdit={this.handleEditMessage}
676               />
677             ))}
678           </div>
679         );
680       }
681     }
682   }
683
684   async handlePageChange(page: number) {
685     this.setState({ page });
686     await this.refetch();
687   }
688
689   async handleUnreadOrAllChange(i: Inbox, event: any) {
690     i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
691     await i.refetch();
692   }
693
694   async handleMessageTypeChange(i: Inbox, event: any) {
695     i.setState({ messageType: Number(event.target.value), page: 1 });
696     await i.refetch();
697   }
698
699   static async fetchInitialData({
700     client,
701     auth,
702   }: InitialFetchRequest): Promise<InboxData> {
703     const sort: CommentSortType = "New";
704
705     return {
706       mentionsRes: auth
707         ? await client.getPersonMentions({
708             sort,
709             unread_only: true,
710             page: 1,
711             limit: fetchLimit,
712             auth,
713           })
714         : { state: "empty" },
715       messagesRes: auth
716         ? await client.getPrivateMessages({
717             unread_only: true,
718             page: 1,
719             limit: fetchLimit,
720             auth,
721           })
722         : { state: "empty" },
723       repliesRes: auth
724         ? await client.getReplies({
725             sort,
726             unread_only: true,
727             page: 1,
728             limit: fetchLimit,
729             auth,
730           })
731         : { state: "empty" },
732     };
733   }
734
735   async refetch() {
736     const sort = this.state.sort;
737     const unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
738     const page = this.state.page;
739     const limit = fetchLimit;
740     const auth = myAuthRequired();
741
742     this.setState({ repliesRes: { state: "loading" } });
743     this.setState({
744       repliesRes: await HttpService.client.getReplies({
745         sort,
746         unread_only,
747         page,
748         limit,
749         auth,
750       }),
751     });
752
753     this.setState({ mentionsRes: { state: "loading" } });
754     this.setState({
755       mentionsRes: await HttpService.client.getPersonMentions({
756         sort,
757         unread_only,
758         page,
759         limit,
760         auth,
761       }),
762     });
763
764     this.setState({ messagesRes: { state: "loading" } });
765     this.setState({
766       messagesRes: await HttpService.client.getPrivateMessages({
767         unread_only,
768         page,
769         limit,
770         auth,
771       }),
772     });
773   }
774
775   async handleSortChange(val: CommentSortType) {
776     this.setState({ sort: val, page: 1 });
777     await this.refetch();
778   }
779
780   async handleMarkAllAsRead(i: Inbox) {
781     i.setState({ markAllAsReadRes: { state: "loading" } });
782
783     i.setState({
784       markAllAsReadRes: await HttpService.client.markAllAsRead({
785         auth: myAuthRequired(),
786       }),
787     });
788
789     if (i.state.markAllAsReadRes.state == "success") {
790       i.setState({
791         repliesRes: { state: "empty" },
792         mentionsRes: { state: "empty" },
793         messagesRes: { state: "empty" },
794       });
795     }
796   }
797
798   async handleAddModToCommunity(form: AddModToCommunity) {
799     // TODO not sure what to do here
800     HttpService.client.addModToCommunity(form);
801   }
802
803   async handlePurgePerson(form: PurgePerson) {
804     const purgePersonRes = await HttpService.client.purgePerson(form);
805     this.purgeItem(purgePersonRes);
806   }
807
808   async handlePurgeComment(form: PurgeComment) {
809     const purgeCommentRes = await HttpService.client.purgeComment(form);
810     this.purgeItem(purgeCommentRes);
811   }
812
813   async handlePurgePost(form: PurgePost) {
814     const purgeRes = await HttpService.client.purgePost(form);
815     this.purgeItem(purgeRes);
816   }
817
818   async handleBlockPerson(form: BlockPerson) {
819     const blockPersonRes = await HttpService.client.blockPerson(form);
820     if (blockPersonRes.state == "success") {
821       updatePersonBlock(blockPersonRes.data);
822     }
823   }
824
825   async handleCreateComment(form: CreateComment) {
826     const res = await HttpService.client.createComment(form);
827
828     if (res.state === "success") {
829       toast(i18n.t("reply_sent"));
830       this.findAndUpdateComment(res);
831     }
832
833     return res;
834   }
835
836   async handleEditComment(form: EditComment) {
837     const res = await HttpService.client.editComment(form);
838
839     if (res.state === "success") {
840       toast(i18n.t("edit"));
841       this.findAndUpdateComment(res);
842     } else if (res.state === "failed") {
843       toast(res.msg, "danger");
844     }
845
846     return res;
847   }
848
849   async handleDeleteComment(form: DeleteComment) {
850     const res = await HttpService.client.deleteComment(form);
851     if (res.state == "success") {
852       toast(i18n.t("deleted"));
853       this.findAndUpdateComment(res);
854     }
855   }
856
857   async handleRemoveComment(form: RemoveComment) {
858     const res = await HttpService.client.removeComment(form);
859     if (res.state == "success") {
860       toast(i18n.t("remove_comment"));
861       this.findAndUpdateComment(res);
862     }
863   }
864
865   async handleSaveComment(form: SaveComment) {
866     const res = await HttpService.client.saveComment(form);
867     this.findAndUpdateComment(res);
868   }
869
870   async handleCommentVote(form: CreateCommentLike) {
871     const res = await HttpService.client.likeComment(form);
872     this.findAndUpdateComment(res);
873   }
874
875   async handleCommentReport(form: CreateCommentReport) {
876     const reportRes = await HttpService.client.createCommentReport(form);
877     this.reportToast(reportRes);
878   }
879
880   async handleDistinguishComment(form: DistinguishComment) {
881     const res = await HttpService.client.distinguishComment(form);
882     this.findAndUpdateComment(res);
883   }
884
885   async handleAddAdmin(form: AddAdmin) {
886     const addAdminRes = await HttpService.client.addAdmin(form);
887
888     if (addAdminRes.state === "success") {
889       this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
890     }
891   }
892
893   async handleTransferCommunity(form: TransferCommunity) {
894     await HttpService.client.transferCommunity(form);
895     toast(i18n.t("transfer_community"));
896   }
897
898   async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
899     const res = await HttpService.client.markCommentReplyAsRead(form);
900     this.findAndUpdateCommentReply(res);
901   }
902
903   async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
904     const res = await HttpService.client.markPersonMentionAsRead(form);
905     this.findAndUpdateMention(res);
906   }
907
908   async handleBanFromCommunity(form: BanFromCommunity) {
909     const banRes = await HttpService.client.banFromCommunity(form);
910     this.updateBanFromCommunity(banRes);
911   }
912
913   async handleBanPerson(form: BanPerson) {
914     const banRes = await HttpService.client.banPerson(form);
915     this.updateBan(banRes);
916   }
917
918   async handleDeleteMessage(form: DeletePrivateMessage) {
919     const res = await HttpService.client.deletePrivateMessage(form);
920     this.findAndUpdateMessage(res);
921   }
922
923   async handleEditMessage(form: EditPrivateMessage) {
924     const res = await HttpService.client.editPrivateMessage(form);
925     this.findAndUpdateMessage(res);
926   }
927
928   async handleMarkMessageAsRead(form: MarkPrivateMessageAsRead) {
929     const res = await HttpService.client.markPrivateMessageAsRead(form);
930     this.findAndUpdateMessage(res);
931   }
932
933   async handleMessageReport(form: CreatePrivateMessageReport) {
934     const res = await HttpService.client.createPrivateMessageReport(form);
935     this.reportToast(res);
936   }
937
938   async handleCreateMessage(form: CreatePrivateMessage) {
939     const res = await HttpService.client.createPrivateMessage(form);
940     this.setState(s => {
941       if (s.messagesRes.state == "success" && res.state == "success") {
942         s.messagesRes.data.private_messages.unshift(
943           res.data.private_message_view
944         );
945       }
946
947       return s;
948     });
949   }
950
951   findAndUpdateMessage(res: RequestState<PrivateMessageResponse>) {
952     this.setState(s => {
953       if (s.messagesRes.state === "success" && res.state === "success") {
954         s.messagesRes.data.private_messages = editPrivateMessage(
955           res.data.private_message_view,
956           s.messagesRes.data.private_messages
957         );
958       }
959       return s;
960     });
961   }
962
963   updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
964     // Maybe not necessary
965     if (banRes.state == "success") {
966       this.setState(s => {
967         if (s.repliesRes.state == "success") {
968           s.repliesRes.data.replies
969             .filter(c => c.creator.id == banRes.data.person_view.person.id)
970             .forEach(
971               c => (c.creator_banned_from_community = banRes.data.banned)
972             );
973         }
974         if (s.mentionsRes.state == "success") {
975           s.mentionsRes.data.mentions
976             .filter(c => c.creator.id == banRes.data.person_view.person.id)
977             .forEach(
978               c => (c.creator_banned_from_community = banRes.data.banned)
979             );
980         }
981         return s;
982       });
983     }
984   }
985
986   updateBan(banRes: RequestState<BanPersonResponse>) {
987     // Maybe not necessary
988     if (banRes.state == "success") {
989       this.setState(s => {
990         if (s.repliesRes.state == "success") {
991           s.repliesRes.data.replies
992             .filter(c => c.creator.id == banRes.data.person_view.person.id)
993             .forEach(c => (c.creator.banned = banRes.data.banned));
994         }
995         if (s.mentionsRes.state == "success") {
996           s.mentionsRes.data.mentions
997             .filter(c => c.creator.id == banRes.data.person_view.person.id)
998             .forEach(c => (c.creator.banned = banRes.data.banned));
999         }
1000         return s;
1001       });
1002     }
1003   }
1004
1005   purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
1006     if (purgeRes.state == "success") {
1007       toast(i18n.t("purge_success"));
1008       this.context.router.history.push(`/`);
1009     }
1010   }
1011
1012   reportToast(
1013     res: RequestState<PrivateMessageReportResponse | CommentReportResponse>
1014   ) {
1015     if (res.state == "success") {
1016       toast(i18n.t("report_created"));
1017     }
1018   }
1019
1020   // A weird case, since you have only replies and mentions, not comment responses
1021   findAndUpdateComment(res: RequestState<CommentResponse>) {
1022     if (res.state == "success") {
1023       this.setState(s => {
1024         if (s.repliesRes.state == "success") {
1025           s.repliesRes.data.replies = editWith(
1026             res.data.comment_view,
1027             s.repliesRes.data.replies
1028           );
1029         }
1030         if (s.mentionsRes.state == "success") {
1031           s.mentionsRes.data.mentions = editWith(
1032             res.data.comment_view,
1033             s.mentionsRes.data.mentions
1034           );
1035         }
1036         // Set finished for the parent
1037         s.finished.set(
1038           getCommentParentId(res.data.comment_view.comment) ?? 0,
1039           true
1040         );
1041         return s;
1042       });
1043     }
1044   }
1045
1046   findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
1047     this.setState(s => {
1048       if (s.repliesRes.state == "success" && res.state == "success") {
1049         s.repliesRes.data.replies = editCommentReply(
1050           res.data.comment_reply_view,
1051           s.repliesRes.data.replies
1052         );
1053       }
1054       return s;
1055     });
1056   }
1057
1058   findAndUpdateMention(res: RequestState<PersonMentionResponse>) {
1059     this.setState(s => {
1060       if (s.mentionsRes.state == "success" && res.state == "success") {
1061         s.mentionsRes.data.mentions = editMention(
1062           res.data.person_mention_view,
1063           s.mentionsRes.data.mentions
1064         );
1065       }
1066       return s;
1067     });
1068   }
1069 }