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