]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/inbox.tsx
Upgrade inferno v8.0.0 try2 (#790)
[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 = {
131         ...this.state,
132         replies:
133           (this.isoData.routeData[0] as GetRepliesResponse).replies || [],
134         mentions:
135           (this.isoData.routeData[1] as GetPersonMentionsResponse).mentions ||
136           [],
137         messages:
138           (this.isoData.routeData[2] as PrivateMessagesResponse)
139             .private_messages || [],
140         loading: false,
141       };
142       this.state = { ...this.state, combined: this.buildCombined() };
143     } else {
144       this.refetch();
145     }
146   }
147
148   componentWillUnmount() {
149     if (isBrowser()) {
150       this.subscription.unsubscribe();
151     }
152   }
153
154   get documentTitle(): string {
155     return this.state.siteRes.site_view.match({
156       some: siteView =>
157         UserService.Instance.myUserInfo.match({
158           some: mui =>
159             `@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${
160               siteView.site.name
161             }`,
162           none: "",
163         }),
164       none: "",
165     });
166   }
167
168   render() {
169     let inboxRss = auth()
170       .ok()
171       .map(a => `/feeds/inbox/${a}.xml`);
172     return (
173       <div className="container">
174         {this.state.loading ? (
175           <h5>
176             <Spinner large />
177           </h5>
178         ) : (
179           <div className="row">
180             <div className="col-12">
181               <HtmlTags
182                 title={this.documentTitle}
183                 path={this.context.router.route.match.url}
184                 description={None}
185                 image={None}
186               />
187               <h5 className="mb-2">
188                 {i18n.t("inbox")}
189                 {inboxRss.match({
190                   some: rss => (
191                     <small>
192                       <a href={rss} title="RSS" rel={relTags}>
193                         <Icon icon="rss" classes="ml-2 text-muted small" />
194                       </a>
195                       <link
196                         rel="alternate"
197                         type="application/atom+xml"
198                         href={rss}
199                       />
200                     </small>
201                   ),
202                   none: <></>,
203                 })}
204               </h5>
205               {this.state.replies.length +
206                 this.state.mentions.length +
207                 this.state.messages.length >
208                 0 &&
209                 this.state.unreadOrAll == UnreadOrAll.Unread && (
210                   <button
211                     className="btn btn-secondary mb-2"
212                     onClick={linkEvent(this, this.markAllAsRead)}
213                   >
214                     {i18n.t("mark_all_as_read")}
215                   </button>
216                 )}
217               {this.selects()}
218               {this.state.messageType == MessageType.All && this.all()}
219               {this.state.messageType == MessageType.Replies && this.replies()}
220               {this.state.messageType == MessageType.Mentions &&
221                 this.mentions()}
222               {this.state.messageType == MessageType.Messages &&
223                 this.messages()}
224               <Paginator
225                 page={this.state.page}
226                 onChange={this.handlePageChange}
227               />
228             </div>
229           </div>
230         )}
231       </div>
232     );
233   }
234
235   unreadOrAllRadios() {
236     return (
237       <div className="btn-group btn-group-toggle flex-wrap mb-2">
238         <label
239           className={`btn btn-outline-secondary pointer
240             ${this.state.unreadOrAll == UnreadOrAll.Unread && "active"}
241           `}
242         >
243           <input
244             type="radio"
245             value={UnreadOrAll.Unread}
246             checked={this.state.unreadOrAll == UnreadOrAll.Unread}
247             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
248           />
249           {i18n.t("unread")}
250         </label>
251         <label
252           className={`btn btn-outline-secondary pointer
253             ${this.state.unreadOrAll == UnreadOrAll.All && "active"}
254           `}
255         >
256           <input
257             type="radio"
258             value={UnreadOrAll.All}
259             checked={this.state.unreadOrAll == UnreadOrAll.All}
260             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
261           />
262           {i18n.t("all")}
263         </label>
264       </div>
265     );
266   }
267
268   messageTypeRadios() {
269     return (
270       <div className="btn-group btn-group-toggle flex-wrap mb-2">
271         <label
272           className={`btn btn-outline-secondary pointer
273             ${this.state.messageType == MessageType.All && "active"}
274           `}
275         >
276           <input
277             type="radio"
278             value={MessageType.All}
279             checked={this.state.messageType == MessageType.All}
280             onChange={linkEvent(this, this.handleMessageTypeChange)}
281           />
282           {i18n.t("all")}
283         </label>
284         <label
285           className={`btn btn-outline-secondary pointer
286             ${this.state.messageType == MessageType.Replies && "active"}
287           `}
288         >
289           <input
290             type="radio"
291             value={MessageType.Replies}
292             checked={this.state.messageType == MessageType.Replies}
293             onChange={linkEvent(this, this.handleMessageTypeChange)}
294           />
295           {i18n.t("replies")}
296         </label>
297         <label
298           className={`btn btn-outline-secondary pointer
299             ${this.state.messageType == MessageType.Mentions && "active"}
300           `}
301         >
302           <input
303             type="radio"
304             value={MessageType.Mentions}
305             checked={this.state.messageType == MessageType.Mentions}
306             onChange={linkEvent(this, this.handleMessageTypeChange)}
307           />
308           {i18n.t("mentions")}
309         </label>
310         <label
311           className={`btn btn-outline-secondary pointer
312             ${this.state.messageType == MessageType.Messages && "active"}
313           `}
314         >
315           <input
316             type="radio"
317             value={MessageType.Messages}
318             checked={this.state.messageType == MessageType.Messages}
319             onChange={linkEvent(this, this.handleMessageTypeChange)}
320           />
321           {i18n.t("messages")}
322         </label>
323       </div>
324     );
325   }
326
327   selects() {
328     return (
329       <div className="mb-2">
330         <span className="mr-3">{this.unreadOrAllRadios()}</span>
331         <span className="mr-3">{this.messageTypeRadios()}</span>
332         <CommentSortSelect
333           sort={this.state.sort}
334           onChange={this.handleSortChange}
335         />
336       </div>
337     );
338   }
339
340   replyToReplyType(r: CommentReplyView): ReplyType {
341     return {
342       id: r.comment_reply.id,
343       type_: ReplyEnum.Reply,
344       view: r,
345       published: r.comment.published,
346     };
347   }
348
349   mentionToReplyType(r: PersonMentionView): ReplyType {
350     return {
351       id: r.person_mention.id,
352       type_: ReplyEnum.Mention,
353       view: r,
354       published: r.comment.published,
355     };
356   }
357
358   messageToReplyType(r: PrivateMessageView): ReplyType {
359     return {
360       id: r.private_message.id,
361       type_: ReplyEnum.Message,
362       view: r,
363       published: r.private_message.published,
364     };
365   }
366
367   buildCombined(): ReplyType[] {
368     let replies: ReplyType[] = this.state.replies.map(r =>
369       this.replyToReplyType(r)
370     );
371     let mentions: ReplyType[] = this.state.mentions.map(r =>
372       this.mentionToReplyType(r)
373     );
374     let messages: ReplyType[] = this.state.messages.map(r =>
375       this.messageToReplyType(r)
376     );
377
378     return [...replies, ...mentions, ...messages].sort((a, b) =>
379       b.published.localeCompare(a.published)
380     );
381   }
382
383   renderReplyType(i: ReplyType) {
384     switch (i.type_) {
385       case ReplyEnum.Reply:
386         return (
387           <CommentNodes
388             key={i.id}
389             nodes={[
390               { comment_view: i.view as CommentView, children: [], depth: 0 },
391             ]}
392             viewType={CommentViewType.Flat}
393             moderators={None}
394             admins={None}
395             maxCommentsShown={None}
396             noIndent
397             markable
398             showCommunity
399             showContext
400             enableDownvotes={enableDownvotes(this.state.siteRes)}
401           />
402         );
403       case ReplyEnum.Mention:
404         return (
405           <CommentNodes
406             key={i.id}
407             nodes={[
408               {
409                 comment_view: i.view as PersonMentionView,
410                 children: [],
411                 depth: 0,
412               },
413             ]}
414             viewType={CommentViewType.Flat}
415             moderators={None}
416             admins={None}
417             maxCommentsShown={None}
418             noIndent
419             markable
420             showCommunity
421             showContext
422             enableDownvotes={enableDownvotes(this.state.siteRes)}
423           />
424         );
425       case ReplyEnum.Message:
426         return (
427           <PrivateMessage
428             key={i.id}
429             private_message_view={i.view as PrivateMessageView}
430           />
431         );
432       default:
433         return <div />;
434     }
435   }
436
437   all() {
438     return <div>{this.state.combined.map(i => this.renderReplyType(i))}</div>;
439   }
440
441   replies() {
442     return (
443       <div>
444         <CommentNodes
445           nodes={commentsToFlatNodes(this.state.replies)}
446           viewType={CommentViewType.Flat}
447           moderators={None}
448           admins={None}
449           maxCommentsShown={None}
450           noIndent
451           markable
452           showCommunity
453           showContext
454           enableDownvotes={enableDownvotes(this.state.siteRes)}
455         />
456       </div>
457     );
458   }
459
460   mentions() {
461     return (
462       <div>
463         {this.state.mentions.map(umv => (
464           <CommentNodes
465             key={umv.person_mention.id}
466             nodes={[{ comment_view: umv, children: [], depth: 0 }]}
467             viewType={CommentViewType.Flat}
468             moderators={None}
469             admins={None}
470             maxCommentsShown={None}
471             noIndent
472             markable
473             showCommunity
474             showContext
475             enableDownvotes={enableDownvotes(this.state.siteRes)}
476           />
477         ))}
478       </div>
479     );
480   }
481
482   messages() {
483     return (
484       <div>
485         {this.state.messages.map(pmv => (
486           <PrivateMessage
487             key={pmv.private_message.id}
488             private_message_view={pmv}
489           />
490         ))}
491       </div>
492     );
493   }
494
495   handlePageChange(page: number) {
496     this.setState({ page });
497     this.refetch();
498   }
499
500   handleUnreadOrAllChange(i: Inbox, event: any) {
501     i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
502     i.refetch();
503   }
504
505   handleMessageTypeChange(i: Inbox, event: any) {
506     i.setState({ messageType: Number(event.target.value), page: 1 });
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.setState({ sort: val, page: 1 });
584     this.refetch();
585   }
586
587   markAllAsRead(i: Inbox) {
588     WebSocketService.Instance.send(
589       wsClient.markAllAsRead({
590         auth: auth().unwrap(),
591       })
592     );
593     i.setState({ replies: [], mentions: [], messages: [] });
594     i.setState({ combined: i.buildCombined() });
595     UserService.Instance.unreadInboxCountSub.next(0);
596     window.scrollTo(0, 0);
597     i.setState(i.state);
598   }
599
600   sendUnreadCount(read: boolean) {
601     let urcs = UserService.Instance.unreadInboxCountSub;
602     if (read) {
603       urcs.next(urcs.getValue() - 1);
604     } else {
605       urcs.next(urcs.getValue() + 1);
606     }
607   }
608
609   parseMessage(msg: any) {
610     let op = wsUserOp(msg);
611     console.log(msg);
612     if (msg.error) {
613       toast(i18n.t(msg.error), "danger");
614       return;
615     } else if (msg.reconnect) {
616       this.refetch();
617     } else if (op == UserOperation.GetReplies) {
618       let data = wsJsonToRes<GetRepliesResponse>(msg, GetRepliesResponse);
619       this.setState({ replies: data.replies });
620       this.setState({ combined: this.buildCombined(), loading: false });
621       window.scrollTo(0, 0);
622       setupTippy();
623     } else if (op == UserOperation.GetPersonMentions) {
624       let data = wsJsonToRes<GetPersonMentionsResponse>(
625         msg,
626         GetPersonMentionsResponse
627       );
628       this.setState({ mentions: data.mentions });
629       this.setState({ combined: this.buildCombined() });
630       window.scrollTo(0, 0);
631       setupTippy();
632     } else if (op == UserOperation.GetPrivateMessages) {
633       let data = wsJsonToRes<PrivateMessagesResponse>(
634         msg,
635         PrivateMessagesResponse
636       );
637       this.setState({ messages: data.private_messages });
638       this.setState({ combined: this.buildCombined() });
639       window.scrollTo(0, 0);
640       setupTippy();
641     } else if (op == UserOperation.EditPrivateMessage) {
642       let data = wsJsonToRes<PrivateMessageResponse>(
643         msg,
644         PrivateMessageResponse
645       );
646       let found: PrivateMessageView = this.state.messages.find(
647         m =>
648           m.private_message.id === data.private_message_view.private_message.id
649       );
650       if (found) {
651         let combinedView = this.state.combined.find(
652           i => i.id == data.private_message_view.private_message.id
653         ).view as PrivateMessageView;
654         found.private_message.content = combinedView.private_message.content =
655           data.private_message_view.private_message.content;
656         found.private_message.updated = combinedView.private_message.updated =
657           data.private_message_view.private_message.updated;
658       }
659       this.setState(this.state);
660     } else if (op == UserOperation.DeletePrivateMessage) {
661       let data = wsJsonToRes<PrivateMessageResponse>(
662         msg,
663         PrivateMessageResponse
664       );
665       let found: PrivateMessageView = this.state.messages.find(
666         m =>
667           m.private_message.id === data.private_message_view.private_message.id
668       );
669       if (found) {
670         let combinedView = this.state.combined.find(
671           i => i.id == data.private_message_view.private_message.id
672         ).view as PrivateMessageView;
673         found.private_message.deleted = combinedView.private_message.deleted =
674           data.private_message_view.private_message.deleted;
675         found.private_message.updated = combinedView.private_message.updated =
676           data.private_message_view.private_message.updated;
677       }
678       this.setState(this.state);
679     } else if (op == UserOperation.MarkPrivateMessageAsRead) {
680       let data = wsJsonToRes<PrivateMessageResponse>(
681         msg,
682         PrivateMessageResponse
683       );
684       let found: PrivateMessageView = this.state.messages.find(
685         m =>
686           m.private_message.id === data.private_message_view.private_message.id
687       );
688
689       if (found) {
690         let combinedView = this.state.combined.find(
691           i =>
692             i.id == data.private_message_view.private_message.id &&
693             i.type_ == ReplyEnum.Message
694         ).view as PrivateMessageView;
695         found.private_message.updated = combinedView.private_message.updated =
696           data.private_message_view.private_message.updated;
697
698         // If youre in the unread view, just remove it from the list
699         if (
700           this.state.unreadOrAll == UnreadOrAll.Unread &&
701           data.private_message_view.private_message.read
702         ) {
703           this.setState({
704             messages: this.state.messages.filter(
705               r =>
706                 r.private_message.id !==
707                 data.private_message_view.private_message.id
708             ),
709           });
710           this.setState({
711             combined: this.state.combined.filter(
712               r => r.id !== data.private_message_view.private_message.id
713             ),
714           });
715         } else {
716           found.private_message.read = combinedView.private_message.read =
717             data.private_message_view.private_message.read;
718         }
719       }
720       this.sendUnreadCount(data.private_message_view.private_message.read);
721       this.setState(this.state);
722     } else if (op == UserOperation.MarkAllAsRead) {
723       // Moved to be instant
724     } else if (
725       op == UserOperation.EditComment ||
726       op == UserOperation.DeleteComment ||
727       op == UserOperation.RemoveComment
728     ) {
729       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
730       editCommentRes(data.comment_view, this.state.replies);
731       this.setState(this.state);
732     } else if (op == UserOperation.MarkCommentReplyAsRead) {
733       let data = wsJsonToRes<CommentReplyResponse>(msg, CommentReplyResponse);
734
735       let found = this.state.replies.find(
736         c => c.comment_reply.id == data.comment_reply_view.comment_reply.id
737       );
738
739       if (found) {
740         let combinedView = this.state.combined.find(
741           i =>
742             i.id == data.comment_reply_view.comment_reply.id &&
743             i.type_ == ReplyEnum.Reply
744         ).view as CommentReplyView;
745         found.comment.content = combinedView.comment.content =
746           data.comment_reply_view.comment.content;
747         found.comment.updated = combinedView.comment.updated =
748           data.comment_reply_view.comment.updated;
749         found.comment.removed = combinedView.comment.removed =
750           data.comment_reply_view.comment.removed;
751         found.comment.deleted = combinedView.comment.deleted =
752           data.comment_reply_view.comment.deleted;
753         found.counts.upvotes = combinedView.counts.upvotes =
754           data.comment_reply_view.counts.upvotes;
755         found.counts.downvotes = combinedView.counts.downvotes =
756           data.comment_reply_view.counts.downvotes;
757         found.counts.score = combinedView.counts.score =
758           data.comment_reply_view.counts.score;
759
760         // If youre in the unread view, just remove it from the list
761         if (
762           this.state.unreadOrAll == UnreadOrAll.Unread &&
763           data.comment_reply_view.comment_reply.read
764         ) {
765           this.setState({
766             replies: this.state.replies.filter(
767               r =>
768                 r.comment_reply.id !== data.comment_reply_view.comment_reply.id
769             ),
770           });
771           this.setState({
772             combined: this.state.combined.filter(
773               r => r.id !== data.comment_reply_view.comment_reply.id
774             ),
775           });
776         } else {
777           found.comment_reply.read = combinedView.comment_reply.read =
778             data.comment_reply_view.comment_reply.read;
779         }
780       }
781       this.sendUnreadCount(data.comment_reply_view.comment_reply.read);
782       this.setState(this.state);
783     } else if (op == UserOperation.MarkPersonMentionAsRead) {
784       let data = wsJsonToRes<PersonMentionResponse>(msg, PersonMentionResponse);
785
786       // TODO this might not be correct, it might need to use the comment id
787       let found = this.state.mentions.find(
788         c => c.person_mention.id == data.person_mention_view.person_mention.id
789       );
790
791       if (found) {
792         let combinedView = this.state.combined.find(
793           i =>
794             i.id == data.person_mention_view.person_mention.id &&
795             i.type_ == ReplyEnum.Mention
796         ).view as PersonMentionView;
797         found.comment.content = combinedView.comment.content =
798           data.person_mention_view.comment.content;
799         found.comment.updated = combinedView.comment.updated =
800           data.person_mention_view.comment.updated;
801         found.comment.removed = combinedView.comment.removed =
802           data.person_mention_view.comment.removed;
803         found.comment.deleted = combinedView.comment.deleted =
804           data.person_mention_view.comment.deleted;
805         found.counts.upvotes = combinedView.counts.upvotes =
806           data.person_mention_view.counts.upvotes;
807         found.counts.downvotes = combinedView.counts.downvotes =
808           data.person_mention_view.counts.downvotes;
809         found.counts.score = combinedView.counts.score =
810           data.person_mention_view.counts.score;
811
812         // If youre in the unread view, just remove it from the list
813         if (
814           this.state.unreadOrAll == UnreadOrAll.Unread &&
815           data.person_mention_view.person_mention.read
816         ) {
817           this.setState({
818             mentions: this.state.mentions.filter(
819               r =>
820                 r.person_mention.id !==
821                 data.person_mention_view.person_mention.id
822             ),
823           });
824           this.setState({
825             combined: this.state.combined.filter(
826               r => r.id !== data.person_mention_view.person_mention.id
827             ),
828           });
829         } else {
830           // TODO test to make sure these mentions are getting marked as read
831           found.person_mention.read = combinedView.person_mention.read =
832             data.person_mention_view.person_mention.read;
833         }
834       }
835       this.sendUnreadCount(data.person_mention_view.person_mention.read);
836       this.setState(this.state);
837     } else if (op == UserOperation.CreatePrivateMessage) {
838       let data = wsJsonToRes<PrivateMessageResponse>(
839         msg,
840         PrivateMessageResponse
841       );
842       UserService.Instance.myUserInfo.match({
843         some: mui => {
844           if (
845             data.private_message_view.recipient.id ==
846             mui.local_user_view.person.id
847           ) {
848             this.state.messages.unshift(data.private_message_view);
849             this.state.combined.unshift(
850               this.messageToReplyType(data.private_message_view)
851             );
852             this.setState(this.state);
853           }
854         },
855         none: void 0,
856       });
857     } else if (op == UserOperation.SaveComment) {
858       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
859       saveCommentRes(data.comment_view, this.state.replies);
860       this.setState(this.state);
861       setupTippy();
862     } else if (op == UserOperation.CreateCommentLike) {
863       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
864       createCommentLikeRes(data.comment_view, this.state.replies);
865       this.setState(this.state);
866     } else if (op == UserOperation.BlockPerson) {
867       let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
868       updatePersonBlock(data);
869     } else if (op == UserOperation.CreatePostReport) {
870       let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
871       if (data) {
872         toast(i18n.t("report_created"));
873       }
874     } else if (op == UserOperation.CreateCommentReport) {
875       let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
876       if (data) {
877         toast(i18n.t("report_created"));
878       }
879     }
880   }
881
882   isMention(view: any): view is PersonMentionView {
883     return (view as PersonMentionView).person_mention !== undefined;
884   }
885
886   isReply(view: any): view is CommentReplyView {
887     return (view as CommentReplyView).comment_reply !== undefined;
888   }
889 }