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