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