]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/inbox.tsx
Adding option types 2 (#689)
[lemmy-ui.git] / src / shared / components / person / inbox.tsx
1 import { None, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import {
4   BlockPersonResponse,
5   CommentReportResponse,
6   CommentResponse,
7   CommentView,
8   GetPersonMentions,
9   GetPersonMentionsResponse,
10   GetPrivateMessages,
11   GetReplies,
12   GetRepliesResponse,
13   GetSiteResponse,
14   PersonMentionResponse,
15   PersonMentionView,
16   PostReportResponse,
17   PrivateMessageResponse,
18   PrivateMessagesResponse,
19   PrivateMessageView,
20   SortType,
21   UserOperation,
22   wsJsonToRes,
23   wsUserOp,
24 } from "lemmy-js-client";
25 import { Subscription } from "rxjs";
26 import { i18n } from "../../i18next";
27 import { InitialFetchRequest } from "../../interfaces";
28 import { UserService, WebSocketService } from "../../services";
29 import {
30   auth,
31   commentsToFlatNodes,
32   createCommentLikeRes,
33   editCommentRes,
34   enableDownvotes,
35   fetchLimit,
36   isBrowser,
37   relTags,
38   saveCommentRes,
39   setIsoData,
40   setupTippy,
41   toast,
42   updatePersonBlock,
43   wsClient,
44   wsSubscribe,
45 } from "../../utils";
46 import { CommentNodes } from "../comment/comment-nodes";
47 import { HtmlTags } from "../common/html-tags";
48 import { Icon, Spinner } from "../common/icon";
49 import { Paginator } from "../common/paginator";
50 import { SortSelect } from "../common/sort-select";
51 import { PrivateMessage } from "../private_message/private-message";
52
53 enum UnreadOrAll {
54   Unread,
55   All,
56 }
57
58 enum MessageType {
59   All,
60   Replies,
61   Mentions,
62   Messages,
63 }
64
65 enum ReplyEnum {
66   Reply,
67   Mention,
68   Message,
69 }
70 type ReplyType = {
71   id: number;
72   type_: ReplyEnum;
73   view: CommentView | PrivateMessageView | PersonMentionView;
74   published: string;
75 };
76
77 interface InboxState {
78   unreadOrAll: UnreadOrAll;
79   messageType: MessageType;
80   replies: CommentView[];
81   mentions: PersonMentionView[];
82   messages: PrivateMessageView[];
83   combined: ReplyType[];
84   sort: SortType;
85   page: number;
86   siteRes: GetSiteResponse;
87   loading: boolean;
88 }
89
90 export class Inbox extends Component<any, InboxState> {
91   private isoData = setIsoData(
92     this.context,
93     GetRepliesResponse,
94     GetPersonMentionsResponse,
95     PrivateMessagesResponse
96   );
97   private subscription: Subscription;
98   private emptyState: InboxState = {
99     unreadOrAll: UnreadOrAll.Unread,
100     messageType: MessageType.All,
101     replies: [],
102     mentions: [],
103     messages: [],
104     combined: [],
105     sort: SortType.New,
106     page: 1,
107     siteRes: this.isoData.site_res,
108     loading: true,
109   };
110
111   constructor(props: any, context: any) {
112     super(props, context);
113
114     this.state = this.emptyState;
115     this.handleSortChange = this.handleSortChange.bind(this);
116     this.handlePageChange = this.handlePageChange.bind(this);
117
118     if (UserService.Instance.myUserInfo.isNone() && isBrowser()) {
119       toast(i18n.t("not_logged_in"), "danger");
120       this.context.router.history.push(`/login`);
121     }
122
123     this.parseMessage = this.parseMessage.bind(this);
124     this.subscription = wsSubscribe(this.parseMessage);
125
126     // Only fetch the data if coming from another route
127     if (this.isoData.path == this.context.router.route.match.url) {
128       this.state.replies =
129         (this.isoData.routeData[0] as GetRepliesResponse).replies || [];
130       this.state.mentions =
131         (this.isoData.routeData[1] as GetPersonMentionsResponse).mentions || [];
132       this.state.messages =
133         (this.isoData.routeData[2] as PrivateMessagesResponse)
134           .private_messages || [];
135       this.state.combined = this.buildCombined();
136       this.state.loading = false;
137     } else {
138       this.refetch();
139     }
140   }
141
142   componentWillUnmount() {
143     if (isBrowser()) {
144       this.subscription.unsubscribe();
145     }
146   }
147
148   get documentTitle(): string {
149     return this.state.siteRes.site_view.match({
150       some: siteView =>
151         UserService.Instance.myUserInfo.match({
152           some: mui =>
153             `@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${
154               siteView.site.name
155             }`,
156           none: "",
157         }),
158       none: "",
159     });
160   }
161
162   render() {
163     let inboxRss = auth()
164       .ok()
165       .map(a => `/feeds/inbox/${a}.xml`);
166     return (
167       <div class="container">
168         {this.state.loading ? (
169           <h5>
170             <Spinner large />
171           </h5>
172         ) : (
173           <div class="row">
174             <div class="col-12">
175               <HtmlTags
176                 title={this.documentTitle}
177                 path={this.context.router.route.match.url}
178                 description={None}
179                 image={None}
180               />
181               <h5 class="mb-2">
182                 {i18n.t("inbox")}
183                 {inboxRss.match({
184                   some: rss => (
185                     <small>
186                       <a href={rss} title="RSS" rel={relTags}>
187                         <Icon icon="rss" classes="ml-2 text-muted small" />
188                       </a>
189                       <link
190                         rel="alternate"
191                         type="application/atom+xml"
192                         href={rss}
193                       />
194                     </small>
195                   ),
196                   none: <></>,
197                 })}
198               </h5>
199               {this.state.replies.length +
200                 this.state.mentions.length +
201                 this.state.messages.length >
202                 0 &&
203                 this.state.unreadOrAll == UnreadOrAll.Unread && (
204                   <button
205                     class="btn btn-secondary mb-2"
206                     onClick={linkEvent(this, this.markAllAsRead)}
207                   >
208                     {i18n.t("mark_all_as_read")}
209                   </button>
210                 )}
211               {this.selects()}
212               {this.state.messageType == MessageType.All && this.all()}
213               {this.state.messageType == MessageType.Replies && this.replies()}
214               {this.state.messageType == MessageType.Mentions &&
215                 this.mentions()}
216               {this.state.messageType == MessageType.Messages &&
217                 this.messages()}
218               <Paginator
219                 page={this.state.page}
220                 onChange={this.handlePageChange}
221               />
222             </div>
223           </div>
224         )}
225       </div>
226     );
227   }
228
229   unreadOrAllRadios() {
230     return (
231       <div class="btn-group btn-group-toggle flex-wrap mb-2">
232         <label
233           className={`btn btn-outline-secondary pointer
234             ${this.state.unreadOrAll == UnreadOrAll.Unread && "active"}
235           `}
236         >
237           <input
238             type="radio"
239             value={UnreadOrAll.Unread}
240             checked={this.state.unreadOrAll == UnreadOrAll.Unread}
241             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
242           />
243           {i18n.t("unread")}
244         </label>
245         <label
246           className={`btn btn-outline-secondary pointer
247             ${this.state.unreadOrAll == UnreadOrAll.All && "active"}
248           `}
249         >
250           <input
251             type="radio"
252             value={UnreadOrAll.All}
253             checked={this.state.unreadOrAll == UnreadOrAll.All}
254             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
255           />
256           {i18n.t("all")}
257         </label>
258       </div>
259     );
260   }
261
262   messageTypeRadios() {
263     return (
264       <div class="btn-group btn-group-toggle flex-wrap mb-2">
265         <label
266           className={`btn btn-outline-secondary pointer
267             ${this.state.messageType == MessageType.All && "active"}
268           `}
269         >
270           <input
271             type="radio"
272             value={MessageType.All}
273             checked={this.state.messageType == MessageType.All}
274             onChange={linkEvent(this, this.handleMessageTypeChange)}
275           />
276           {i18n.t("all")}
277         </label>
278         <label
279           className={`btn btn-outline-secondary pointer
280             ${this.state.messageType == MessageType.Replies && "active"}
281           `}
282         >
283           <input
284             type="radio"
285             value={MessageType.Replies}
286             checked={this.state.messageType == MessageType.Replies}
287             onChange={linkEvent(this, this.handleMessageTypeChange)}
288           />
289           {i18n.t("replies")}
290         </label>
291         <label
292           className={`btn btn-outline-secondary pointer
293             ${this.state.messageType == MessageType.Mentions && "active"}
294           `}
295         >
296           <input
297             type="radio"
298             value={MessageType.Mentions}
299             checked={this.state.messageType == MessageType.Mentions}
300             onChange={linkEvent(this, this.handleMessageTypeChange)}
301           />
302           {i18n.t("mentions")}
303         </label>
304         <label
305           className={`btn btn-outline-secondary pointer
306             ${this.state.messageType == MessageType.Messages && "active"}
307           `}
308         >
309           <input
310             type="radio"
311             value={MessageType.Messages}
312             checked={this.state.messageType == MessageType.Messages}
313             onChange={linkEvent(this, this.handleMessageTypeChange)}
314           />
315           {i18n.t("messages")}
316         </label>
317       </div>
318     );
319   }
320
321   selects() {
322     return (
323       <div className="mb-2">
324         <span class="mr-3">{this.unreadOrAllRadios()}</span>
325         <span class="mr-3">{this.messageTypeRadios()}</span>
326         <SortSelect
327           sort={this.state.sort}
328           onChange={this.handleSortChange}
329           hideHot
330           hideMostComments
331         />
332       </div>
333     );
334   }
335
336   replyToReplyType(r: CommentView): ReplyType {
337     return {
338       id: r.comment.id,
339       type_: ReplyEnum.Reply,
340       view: r,
341       published: r.comment.published,
342     };
343   }
344
345   mentionToReplyType(r: PersonMentionView): ReplyType {
346     return {
347       id: r.person_mention.id,
348       type_: ReplyEnum.Mention,
349       view: r,
350       published: r.comment.published,
351     };
352   }
353
354   messageToReplyType(r: PrivateMessageView): ReplyType {
355     return {
356       id: r.private_message.id,
357       type_: ReplyEnum.Message,
358       view: r,
359       published: r.private_message.published,
360     };
361   }
362
363   buildCombined(): ReplyType[] {
364     let replies: ReplyType[] = this.state.replies.map(r =>
365       this.replyToReplyType(r)
366     );
367     let mentions: ReplyType[] = this.state.mentions.map(r =>
368       this.mentionToReplyType(r)
369     );
370     let messages: ReplyType[] = this.state.messages.map(r =>
371       this.messageToReplyType(r)
372     );
373
374     return [...replies, ...mentions, ...messages].sort((a, b) =>
375       b.published.localeCompare(a.published)
376     );
377   }
378
379   renderReplyType(i: ReplyType) {
380     switch (i.type_) {
381       case ReplyEnum.Reply:
382         return (
383           <CommentNodes
384             key={i.id}
385             nodes={[{ comment_view: i.view as CommentView }]}
386             moderators={None}
387             admins={None}
388             maxCommentsShown={None}
389             noIndent
390             markable
391             showCommunity
392             showContext
393             enableDownvotes={enableDownvotes(this.state.siteRes)}
394           />
395         );
396       case ReplyEnum.Mention:
397         return (
398           <CommentNodes
399             key={i.id}
400             nodes={[{ comment_view: i.view as PersonMentionView }]}
401             moderators={None}
402             admins={None}
403             maxCommentsShown={None}
404             noIndent
405             markable
406             showCommunity
407             showContext
408             enableDownvotes={enableDownvotes(this.state.siteRes)}
409           />
410         );
411       case ReplyEnum.Message:
412         return (
413           <PrivateMessage
414             key={i.id}
415             private_message_view={i.view as PrivateMessageView}
416           />
417         );
418       default:
419         return <div />;
420     }
421   }
422
423   all() {
424     return <div>{this.state.combined.map(i => this.renderReplyType(i))}</div>;
425   }
426
427   replies() {
428     return (
429       <div>
430         <CommentNodes
431           nodes={commentsToFlatNodes(this.state.replies)}
432           moderators={None}
433           admins={None}
434           maxCommentsShown={None}
435           noIndent
436           markable
437           showCommunity
438           showContext
439           enableDownvotes={enableDownvotes(this.state.siteRes)}
440         />
441       </div>
442     );
443   }
444
445   mentions() {
446     return (
447       <div>
448         {this.state.mentions.map(umv => (
449           <CommentNodes
450             key={umv.person_mention.id}
451             nodes={[{ comment_view: umv }]}
452             moderators={None}
453             admins={None}
454             maxCommentsShown={None}
455             noIndent
456             markable
457             showCommunity
458             showContext
459             enableDownvotes={enableDownvotes(this.state.siteRes)}
460           />
461         ))}
462       </div>
463     );
464   }
465
466   messages() {
467     return (
468       <div>
469         {this.state.messages.map(pmv => (
470           <PrivateMessage
471             key={pmv.private_message.id}
472             private_message_view={pmv}
473           />
474         ))}
475       </div>
476     );
477   }
478
479   handlePageChange(page: number) {
480     this.setState({ page });
481     this.refetch();
482   }
483
484   handleUnreadOrAllChange(i: Inbox, event: any) {
485     i.state.unreadOrAll = Number(event.target.value);
486     i.state.page = 1;
487     i.setState(i.state);
488     i.refetch();
489   }
490
491   handleMessageTypeChange(i: Inbox, event: any) {
492     i.state.messageType = Number(event.target.value);
493     i.state.page = 1;
494     i.setState(i.state);
495     i.refetch();
496   }
497
498   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
499     let promises: Promise<any>[] = [];
500
501     // It can be /u/me, or /username/1
502     let repliesForm = new GetReplies({
503       sort: Some(SortType.New),
504       unread_only: Some(true),
505       page: Some(1),
506       limit: Some(fetchLimit),
507       auth: req.auth.unwrap(),
508     });
509     promises.push(req.client.getReplies(repliesForm));
510
511     let personMentionsForm = new GetPersonMentions({
512       sort: Some(SortType.New),
513       unread_only: Some(true),
514       page: Some(1),
515       limit: Some(fetchLimit),
516       auth: req.auth.unwrap(),
517     });
518     promises.push(req.client.getPersonMentions(personMentionsForm));
519
520     let privateMessagesForm = new GetPrivateMessages({
521       unread_only: Some(true),
522       page: Some(1),
523       limit: Some(fetchLimit),
524       auth: req.auth.unwrap(),
525     });
526     promises.push(req.client.getPrivateMessages(privateMessagesForm));
527
528     return promises;
529   }
530
531   refetch() {
532     let sort = Some(this.state.sort);
533     let unread_only = Some(this.state.unreadOrAll == UnreadOrAll.Unread);
534     let page = Some(this.state.page);
535     let limit = Some(fetchLimit);
536
537     let repliesForm = new GetReplies({
538       sort,
539       unread_only,
540       page,
541       limit,
542       auth: auth().unwrap(),
543     });
544     WebSocketService.Instance.send(wsClient.getReplies(repliesForm));
545
546     let personMentionsForm = new GetPersonMentions({
547       sort,
548       unread_only,
549       page,
550       limit,
551       auth: auth().unwrap(),
552     });
553     WebSocketService.Instance.send(
554       wsClient.getPersonMentions(personMentionsForm)
555     );
556
557     let privateMessagesForm = new GetPrivateMessages({
558       unread_only,
559       page,
560       limit,
561       auth: auth().unwrap(),
562     });
563     WebSocketService.Instance.send(
564       wsClient.getPrivateMessages(privateMessagesForm)
565     );
566   }
567
568   handleSortChange(val: SortType) {
569     this.state.sort = val;
570     this.state.page = 1;
571     this.setState(this.state);
572     this.refetch();
573   }
574
575   markAllAsRead(i: Inbox) {
576     WebSocketService.Instance.send(
577       wsClient.markAllAsRead({
578         auth: auth().unwrap(),
579       })
580     );
581     i.state.replies = [];
582     i.state.mentions = [];
583     i.state.messages = [];
584     UserService.Instance.unreadInboxCountSub.next(0);
585     window.scrollTo(0, 0);
586     i.setState(i.state);
587   }
588
589   sendUnreadCount(read: boolean) {
590     let urcs = UserService.Instance.unreadInboxCountSub;
591     if (read) {
592       urcs.next(urcs.getValue() - 1);
593     } else {
594       urcs.next(urcs.getValue() + 1);
595     }
596   }
597
598   parseMessage(msg: any) {
599     let op = wsUserOp(msg);
600     console.log(msg);
601     if (msg.error) {
602       toast(i18n.t(msg.error), "danger");
603       return;
604     } else if (msg.reconnect) {
605       this.refetch();
606     } else if (op == UserOperation.GetReplies) {
607       let data = wsJsonToRes<GetRepliesResponse>(msg, GetRepliesResponse);
608       this.state.replies = data.replies;
609       this.state.combined = this.buildCombined();
610       this.state.loading = false;
611       window.scrollTo(0, 0);
612       this.setState(this.state);
613       setupTippy();
614     } else if (op == UserOperation.GetPersonMentions) {
615       let data = wsJsonToRes<GetPersonMentionsResponse>(
616         msg,
617         GetPersonMentionsResponse
618       );
619       this.state.mentions = data.mentions;
620       this.state.combined = this.buildCombined();
621       window.scrollTo(0, 0);
622       this.setState(this.state);
623       setupTippy();
624     } else if (op == UserOperation.GetPrivateMessages) {
625       let data = wsJsonToRes<PrivateMessagesResponse>(
626         msg,
627         PrivateMessagesResponse
628       );
629       this.state.messages = data.private_messages;
630       this.state.combined = this.buildCombined();
631       window.scrollTo(0, 0);
632       this.setState(this.state);
633       setupTippy();
634     } else if (op == UserOperation.EditPrivateMessage) {
635       let data = wsJsonToRes<PrivateMessageResponse>(
636         msg,
637         PrivateMessageResponse
638       );
639       let found: PrivateMessageView = this.state.messages.find(
640         m =>
641           m.private_message.id === data.private_message_view.private_message.id
642       );
643       if (found) {
644         let combinedView = this.state.combined.find(
645           i => i.id == data.private_message_view.private_message.id
646         ).view as PrivateMessageView;
647         found.private_message.content = combinedView.private_message.content =
648           data.private_message_view.private_message.content;
649         found.private_message.updated = combinedView.private_message.updated =
650           data.private_message_view.private_message.updated;
651       }
652       this.setState(this.state);
653     } else if (op == UserOperation.DeletePrivateMessage) {
654       let data = wsJsonToRes<PrivateMessageResponse>(
655         msg,
656         PrivateMessageResponse
657       );
658       let found: PrivateMessageView = this.state.messages.find(
659         m =>
660           m.private_message.id === data.private_message_view.private_message.id
661       );
662       if (found) {
663         let combinedView = this.state.combined.find(
664           i => i.id == data.private_message_view.private_message.id
665         ).view as PrivateMessageView;
666         found.private_message.deleted = combinedView.private_message.deleted =
667           data.private_message_view.private_message.deleted;
668         found.private_message.updated = combinedView.private_message.updated =
669           data.private_message_view.private_message.updated;
670       }
671       this.setState(this.state);
672     } else if (op == UserOperation.MarkPrivateMessageAsRead) {
673       let data = wsJsonToRes<PrivateMessageResponse>(
674         msg,
675         PrivateMessageResponse
676       );
677       let found: PrivateMessageView = this.state.messages.find(
678         m =>
679           m.private_message.id === data.private_message_view.private_message.id
680       );
681
682       if (found) {
683         let combinedView = this.state.combined.find(
684           i => i.id == data.private_message_view.private_message.id
685         ).view as PrivateMessageView;
686         found.private_message.updated = combinedView.private_message.updated =
687           data.private_message_view.private_message.updated;
688
689         // If youre in the unread view, just remove it from the list
690         if (
691           this.state.unreadOrAll == UnreadOrAll.Unread &&
692           data.private_message_view.private_message.read
693         ) {
694           this.state.messages = this.state.messages.filter(
695             r =>
696               r.private_message.id !==
697               data.private_message_view.private_message.id
698           );
699           this.state.combined = this.state.combined.filter(
700             r => r.id !== data.private_message_view.private_message.id
701           );
702         } else {
703           found.private_message.read = combinedView.private_message.read =
704             data.private_message_view.private_message.read;
705         }
706       }
707       this.sendUnreadCount(data.private_message_view.private_message.read);
708       this.setState(this.state);
709     } else if (op == UserOperation.MarkAllAsRead) {
710       // Moved to be instant
711     } else if (
712       op == UserOperation.EditComment ||
713       op == UserOperation.DeleteComment ||
714       op == UserOperation.RemoveComment
715     ) {
716       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
717       editCommentRes(data.comment_view, this.state.replies);
718       this.setState(this.state);
719     } else if (op == UserOperation.MarkCommentAsRead) {
720       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
721
722       // If youre in the unread view, just remove it from the list
723       if (
724         this.state.unreadOrAll == UnreadOrAll.Unread &&
725         data.comment_view.comment.read
726       ) {
727         this.state.replies = this.state.replies.filter(
728           r => r.comment.id !== data.comment_view.comment.id
729         );
730         this.state.combined = this.state.combined.filter(
731           r => r.id !== data.comment_view.comment.id
732         );
733       } else {
734         let found = this.state.replies.find(
735           c => c.comment.id == data.comment_view.comment.id
736         );
737         let combinedView = this.state.combined.find(
738           i => i.id == data.comment_view.comment.id
739         ).view as CommentView;
740         found.comment.read = combinedView.comment.read =
741           data.comment_view.comment.read;
742       }
743
744       this.sendUnreadCount(data.comment_view.comment.read);
745       this.setState(this.state);
746       setupTippy();
747     } else if (op == UserOperation.MarkPersonMentionAsRead) {
748       let data = wsJsonToRes<PersonMentionResponse>(msg, PersonMentionResponse);
749
750       // TODO this might not be correct, it might need to use the comment id
751       let found = this.state.mentions.find(
752         c => c.person_mention.id == data.person_mention_view.person_mention.id
753       );
754
755       if (found) {
756         let combinedView = this.state.combined.find(
757           i => i.id == data.person_mention_view.person_mention.id
758         ).view as PersonMentionView;
759         found.comment.content = combinedView.comment.content =
760           data.person_mention_view.comment.content;
761         found.comment.updated = combinedView.comment.updated =
762           data.person_mention_view.comment.updated;
763         found.comment.removed = combinedView.comment.removed =
764           data.person_mention_view.comment.removed;
765         found.comment.deleted = combinedView.comment.deleted =
766           data.person_mention_view.comment.deleted;
767         found.counts.upvotes = combinedView.counts.upvotes =
768           data.person_mention_view.counts.upvotes;
769         found.counts.downvotes = combinedView.counts.downvotes =
770           data.person_mention_view.counts.downvotes;
771         found.counts.score = combinedView.counts.score =
772           data.person_mention_view.counts.score;
773
774         // If youre in the unread view, just remove it from the list
775         if (
776           this.state.unreadOrAll == UnreadOrAll.Unread &&
777           data.person_mention_view.person_mention.read
778         ) {
779           this.state.mentions = this.state.mentions.filter(
780             r =>
781               r.person_mention.id !== data.person_mention_view.person_mention.id
782           );
783           this.state.combined = this.state.combined.filter(
784             r => r.id !== data.person_mention_view.person_mention.id
785           );
786         } else {
787           // TODO test to make sure these mentions are getting marked as read
788           found.person_mention.read = combinedView.person_mention.read =
789             data.person_mention_view.person_mention.read;
790         }
791       }
792       this.sendUnreadCount(data.person_mention_view.person_mention.read);
793       this.setState(this.state);
794     } else if (op == UserOperation.CreateComment) {
795       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
796
797       UserService.Instance.myUserInfo.match({
798         some: mui => {
799           if (data.recipient_ids.includes(mui.local_user_view.local_user.id)) {
800             this.state.replies.unshift(data.comment_view);
801             this.state.combined.unshift(
802               this.replyToReplyType(data.comment_view)
803             );
804             this.setState(this.state);
805           } else if (
806             data.comment_view.creator.id == mui.local_user_view.person.id
807           ) {
808             // If youre in the unread view, just remove it from the list
809             if (this.state.unreadOrAll == UnreadOrAll.Unread) {
810               this.state.replies = this.state.replies.filter(
811                 r =>
812                   r.comment.id !==
813                   data.comment_view.comment.parent_id.unwrapOr(0)
814               );
815               this.state.mentions = this.state.mentions.filter(
816                 m =>
817                   m.comment.id !==
818                   data.comment_view.comment.parent_id.unwrapOr(0)
819               );
820               this.state.combined = this.state.combined.filter(r => {
821                 if (this.isMention(r.view))
822                   return (
823                     r.view.comment.id !==
824                     data.comment_view.comment.parent_id.unwrapOr(0)
825                   );
826                 else
827                   return (
828                     r.id !== data.comment_view.comment.parent_id.unwrapOr(0)
829                   );
830               });
831             } else {
832               let mention_found = this.state.mentions.find(
833                 i =>
834                   i.comment.id ==
835                   data.comment_view.comment.parent_id.unwrapOr(0)
836               );
837               if (mention_found) {
838                 mention_found.person_mention.read = true;
839               }
840               let reply_found = this.state.replies.find(
841                 i =>
842                   i.comment.id ==
843                   data.comment_view.comment.parent_id.unwrapOr(0)
844               );
845               if (reply_found) {
846                 reply_found.comment.read = true;
847               }
848               this.state.combined = this.buildCombined();
849             }
850             this.sendUnreadCount(true);
851             this.setState(this.state);
852             setupTippy();
853             // TODO this seems wrong, you should be using form_id
854             toast(i18n.t("reply_sent"));
855           }
856         },
857         none: void 0,
858       });
859     } else if (op == UserOperation.CreatePrivateMessage) {
860       let data = wsJsonToRes<PrivateMessageResponse>(
861         msg,
862         PrivateMessageResponse
863       );
864       UserService.Instance.myUserInfo.match({
865         some: mui => {
866           if (
867             data.private_message_view.recipient.id ==
868             mui.local_user_view.person.id
869           ) {
870             this.state.messages.unshift(data.private_message_view);
871             this.state.combined.unshift(
872               this.messageToReplyType(data.private_message_view)
873             );
874             this.setState(this.state);
875           }
876         },
877         none: void 0,
878       });
879     } else if (op == UserOperation.SaveComment) {
880       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
881       saveCommentRes(data.comment_view, this.state.replies);
882       this.setState(this.state);
883       setupTippy();
884     } else if (op == UserOperation.CreateCommentLike) {
885       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
886       createCommentLikeRes(data.comment_view, this.state.replies);
887       this.setState(this.state);
888     } else if (op == UserOperation.BlockPerson) {
889       let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
890       updatePersonBlock(data);
891     } else if (op == UserOperation.CreatePostReport) {
892       let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
893       if (data) {
894         toast(i18n.t("report_created"));
895       }
896     } else if (op == UserOperation.CreateCommentReport) {
897       let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
898       if (data) {
899         toast(i18n.t("report_created"));
900       }
901     }
902   }
903
904   isMention(view: any): view is PersonMentionView {
905     return (view as PersonMentionView).person_mention !== undefined;
906   }
907 }