]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/inbox.tsx
Adding new site setup fields. (#840)
[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           />
400         );
401       case ReplyEnum.Mention:
402         return (
403           <CommentNodes
404             key={i.id}
405             nodes={[
406               {
407                 comment_view: i.view as PersonMentionView,
408                 children: [],
409                 depth: 0,
410               },
411             ]}
412             viewType={CommentViewType.Flat}
413             moderators={None}
414             admins={None}
415             maxCommentsShown={None}
416             noIndent
417             markable
418             showCommunity
419             showContext
420             enableDownvotes={enableDownvotes(this.state.siteRes)}
421             allLanguages={this.state.siteRes.all_languages}
422           />
423         );
424       case ReplyEnum.Message:
425         return (
426           <PrivateMessage
427             key={i.id}
428             private_message_view={i.view as PrivateMessageView}
429           />
430         );
431       default:
432         return <div />;
433     }
434   }
435
436   all() {
437     return <div>{this.state.combined.map(i => this.renderReplyType(i))}</div>;
438   }
439
440   replies() {
441     return (
442       <div>
443         <CommentNodes
444           nodes={commentsToFlatNodes(this.state.replies)}
445           viewType={CommentViewType.Flat}
446           moderators={None}
447           admins={None}
448           maxCommentsShown={None}
449           noIndent
450           markable
451           showCommunity
452           showContext
453           enableDownvotes={enableDownvotes(this.state.siteRes)}
454           allLanguages={this.state.siteRes.all_languages}
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             allLanguages={this.state.siteRes.all_languages}
477           />
478         ))}
479       </div>
480     );
481   }
482
483   messages() {
484     return (
485       <div>
486         {this.state.messages.map(pmv => (
487           <PrivateMessage
488             key={pmv.private_message.id}
489             private_message_view={pmv}
490           />
491         ))}
492       </div>
493     );
494   }
495
496   handlePageChange(page: number) {
497     this.setState({ page });
498     this.refetch();
499   }
500
501   handleUnreadOrAllChange(i: Inbox, event: any) {
502     i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
503     i.refetch();
504   }
505
506   handleMessageTypeChange(i: Inbox, event: any) {
507     i.setState({ messageType: Number(event.target.value), page: 1 });
508     i.refetch();
509   }
510
511   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
512     let promises: Promise<any>[] = [];
513
514     let sort = Some(CommentSortType.New);
515
516     // It can be /u/me, or /username/1
517     let repliesForm = new GetReplies({
518       sort,
519       unread_only: Some(true),
520       page: Some(1),
521       limit: Some(fetchLimit),
522       auth: req.auth.unwrap(),
523     });
524     promises.push(req.client.getReplies(repliesForm));
525
526     let personMentionsForm = new GetPersonMentions({
527       sort,
528       unread_only: Some(true),
529       page: Some(1),
530       limit: Some(fetchLimit),
531       auth: req.auth.unwrap(),
532     });
533     promises.push(req.client.getPersonMentions(personMentionsForm));
534
535     let privateMessagesForm = new GetPrivateMessages({
536       unread_only: Some(true),
537       page: Some(1),
538       limit: Some(fetchLimit),
539       auth: req.auth.unwrap(),
540     });
541     promises.push(req.client.getPrivateMessages(privateMessagesForm));
542
543     return promises;
544   }
545
546   refetch() {
547     let sort = Some(this.state.sort);
548     let unread_only = Some(this.state.unreadOrAll == UnreadOrAll.Unread);
549     let page = Some(this.state.page);
550     let limit = Some(fetchLimit);
551
552     let repliesForm = new GetReplies({
553       sort,
554       unread_only,
555       page,
556       limit,
557       auth: auth().unwrap(),
558     });
559     WebSocketService.Instance.send(wsClient.getReplies(repliesForm));
560
561     let personMentionsForm = new GetPersonMentions({
562       sort,
563       unread_only,
564       page,
565       limit,
566       auth: auth().unwrap(),
567     });
568     WebSocketService.Instance.send(
569       wsClient.getPersonMentions(personMentionsForm)
570     );
571
572     let privateMessagesForm = new GetPrivateMessages({
573       unread_only,
574       page,
575       limit,
576       auth: auth().unwrap(),
577     });
578     WebSocketService.Instance.send(
579       wsClient.getPrivateMessages(privateMessagesForm)
580     );
581   }
582
583   handleSortChange(val: CommentSortType) {
584     this.setState({ sort: val, page: 1 });
585     this.refetch();
586   }
587
588   markAllAsRead(i: Inbox) {
589     WebSocketService.Instance.send(
590       wsClient.markAllAsRead({
591         auth: auth().unwrap(),
592       })
593     );
594     i.setState({ replies: [], mentions: [], messages: [] });
595     i.setState({ combined: i.buildCombined() });
596     UserService.Instance.unreadInboxCountSub.next(0);
597     window.scrollTo(0, 0);
598     i.setState(i.state);
599   }
600
601   sendUnreadCount(read: boolean) {
602     let urcs = UserService.Instance.unreadInboxCountSub;
603     if (read) {
604       urcs.next(urcs.getValue() - 1);
605     } else {
606       urcs.next(urcs.getValue() + 1);
607     }
608   }
609
610   parseMessage(msg: any) {
611     let op = wsUserOp(msg);
612     console.log(msg);
613     if (msg.error) {
614       toast(i18n.t(msg.error), "danger");
615       return;
616     } else if (msg.reconnect) {
617       this.refetch();
618     } else if (op == UserOperation.GetReplies) {
619       let data = wsJsonToRes<GetRepliesResponse>(msg, GetRepliesResponse);
620       this.setState({ replies: data.replies });
621       this.setState({ combined: this.buildCombined(), loading: false });
622       window.scrollTo(0, 0);
623       setupTippy();
624     } else if (op == UserOperation.GetPersonMentions) {
625       let data = wsJsonToRes<GetPersonMentionsResponse>(
626         msg,
627         GetPersonMentionsResponse
628       );
629       this.setState({ mentions: data.mentions });
630       this.setState({ combined: this.buildCombined() });
631       window.scrollTo(0, 0);
632       setupTippy();
633     } else if (op == UserOperation.GetPrivateMessages) {
634       let data = wsJsonToRes<PrivateMessagesResponse>(
635         msg,
636         PrivateMessagesResponse
637       );
638       this.setState({ messages: data.private_messages });
639       this.setState({ combined: this.buildCombined() });
640       window.scrollTo(0, 0);
641       setupTippy();
642     } else if (op == UserOperation.EditPrivateMessage) {
643       let data = wsJsonToRes<PrivateMessageResponse>(
644         msg,
645         PrivateMessageResponse
646       );
647       let found: PrivateMessageView = this.state.messages.find(
648         m =>
649           m.private_message.id === data.private_message_view.private_message.id
650       );
651       if (found) {
652         let combinedView = this.state.combined.find(
653           i => i.id == data.private_message_view.private_message.id
654         ).view as PrivateMessageView;
655         found.private_message.content = combinedView.private_message.content =
656           data.private_message_view.private_message.content;
657         found.private_message.updated = combinedView.private_message.updated =
658           data.private_message_view.private_message.updated;
659       }
660       this.setState(this.state);
661     } else if (op == UserOperation.DeletePrivateMessage) {
662       let data = wsJsonToRes<PrivateMessageResponse>(
663         msg,
664         PrivateMessageResponse
665       );
666       let found: PrivateMessageView = this.state.messages.find(
667         m =>
668           m.private_message.id === data.private_message_view.private_message.id
669       );
670       if (found) {
671         let combinedView = this.state.combined.find(
672           i => i.id == data.private_message_view.private_message.id
673         ).view as PrivateMessageView;
674         found.private_message.deleted = combinedView.private_message.deleted =
675           data.private_message_view.private_message.deleted;
676         found.private_message.updated = combinedView.private_message.updated =
677           data.private_message_view.private_message.updated;
678       }
679       this.setState(this.state);
680     } else if (op == UserOperation.MarkPrivateMessageAsRead) {
681       let data = wsJsonToRes<PrivateMessageResponse>(
682         msg,
683         PrivateMessageResponse
684       );
685       let found: PrivateMessageView = this.state.messages.find(
686         m =>
687           m.private_message.id === data.private_message_view.private_message.id
688       );
689
690       if (found) {
691         let combinedView = this.state.combined.find(
692           i =>
693             i.id == data.private_message_view.private_message.id &&
694             i.type_ == ReplyEnum.Message
695         ).view as PrivateMessageView;
696         found.private_message.updated = combinedView.private_message.updated =
697           data.private_message_view.private_message.updated;
698
699         // If youre in the unread view, just remove it from the list
700         if (
701           this.state.unreadOrAll == UnreadOrAll.Unread &&
702           data.private_message_view.private_message.read
703         ) {
704           this.setState({
705             messages: this.state.messages.filter(
706               r =>
707                 r.private_message.id !==
708                 data.private_message_view.private_message.id
709             ),
710           });
711           this.setState({
712             combined: this.state.combined.filter(
713               r => r.id !== data.private_message_view.private_message.id
714             ),
715           });
716         } else {
717           found.private_message.read = combinedView.private_message.read =
718             data.private_message_view.private_message.read;
719         }
720       }
721       this.sendUnreadCount(data.private_message_view.private_message.read);
722       this.setState(this.state);
723     } else if (op == UserOperation.MarkAllAsRead) {
724       // Moved to be instant
725     } else if (
726       op == UserOperation.EditComment ||
727       op == UserOperation.DeleteComment ||
728       op == UserOperation.RemoveComment
729     ) {
730       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
731       editCommentRes(data.comment_view, this.state.replies);
732       this.setState(this.state);
733     } else if (op == UserOperation.MarkCommentReplyAsRead) {
734       let data = wsJsonToRes<CommentReplyResponse>(msg, CommentReplyResponse);
735
736       let found = this.state.replies.find(
737         c => c.comment_reply.id == data.comment_reply_view.comment_reply.id
738       );
739
740       if (found) {
741         let combinedView = this.state.combined.find(
742           i =>
743             i.id == data.comment_reply_view.comment_reply.id &&
744             i.type_ == ReplyEnum.Reply
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.setState({
767             replies: this.state.replies.filter(
768               r =>
769                 r.comment_reply.id !== data.comment_reply_view.comment_reply.id
770             ),
771           });
772           this.setState({
773             combined: this.state.combined.filter(
774               r => r.id !== data.comment_reply_view.comment_reply.id
775             ),
776           });
777         } else {
778           found.comment_reply.read = combinedView.comment_reply.read =
779             data.comment_reply_view.comment_reply.read;
780         }
781       }
782       this.sendUnreadCount(data.comment_reply_view.comment_reply.read);
783       this.setState(this.state);
784     } else if (op == UserOperation.MarkPersonMentionAsRead) {
785       let data = wsJsonToRes<PersonMentionResponse>(msg, PersonMentionResponse);
786
787       // TODO this might not be correct, it might need to use the comment id
788       let found = this.state.mentions.find(
789         c => c.person_mention.id == data.person_mention_view.person_mention.id
790       );
791
792       if (found) {
793         let combinedView = this.state.combined.find(
794           i =>
795             i.id == data.person_mention_view.person_mention.id &&
796             i.type_ == ReplyEnum.Mention
797         ).view as PersonMentionView;
798         found.comment.content = combinedView.comment.content =
799           data.person_mention_view.comment.content;
800         found.comment.updated = combinedView.comment.updated =
801           data.person_mention_view.comment.updated;
802         found.comment.removed = combinedView.comment.removed =
803           data.person_mention_view.comment.removed;
804         found.comment.deleted = combinedView.comment.deleted =
805           data.person_mention_view.comment.deleted;
806         found.counts.upvotes = combinedView.counts.upvotes =
807           data.person_mention_view.counts.upvotes;
808         found.counts.downvotes = combinedView.counts.downvotes =
809           data.person_mention_view.counts.downvotes;
810         found.counts.score = combinedView.counts.score =
811           data.person_mention_view.counts.score;
812
813         // If youre in the unread view, just remove it from the list
814         if (
815           this.state.unreadOrAll == UnreadOrAll.Unread &&
816           data.person_mention_view.person_mention.read
817         ) {
818           this.setState({
819             mentions: this.state.mentions.filter(
820               r =>
821                 r.person_mention.id !==
822                 data.person_mention_view.person_mention.id
823             ),
824           });
825           this.setState({
826             combined: this.state.combined.filter(
827               r => r.id !== data.person_mention_view.person_mention.id
828             ),
829           });
830         } else {
831           // TODO test to make sure these mentions are getting marked as read
832           found.person_mention.read = combinedView.person_mention.read =
833             data.person_mention_view.person_mention.read;
834         }
835       }
836       this.sendUnreadCount(data.person_mention_view.person_mention.read);
837       this.setState(this.state);
838     } else if (op == UserOperation.CreatePrivateMessage) {
839       let data = wsJsonToRes<PrivateMessageResponse>(
840         msg,
841         PrivateMessageResponse
842       );
843       UserService.Instance.myUserInfo.match({
844         some: mui => {
845           if (
846             data.private_message_view.recipient.id ==
847             mui.local_user_view.person.id
848           ) {
849             this.state.messages.unshift(data.private_message_view);
850             this.state.combined.unshift(
851               this.messageToReplyType(data.private_message_view)
852             );
853             this.setState(this.state);
854           }
855         },
856         none: void 0,
857       });
858     } else if (op == UserOperation.SaveComment) {
859       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
860       saveCommentRes(data.comment_view, this.state.replies);
861       this.setState(this.state);
862       setupTippy();
863     } else if (op == UserOperation.CreateCommentLike) {
864       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
865       createCommentLikeRes(data.comment_view, this.state.replies);
866       this.setState(this.state);
867     } else if (op == UserOperation.BlockPerson) {
868       let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
869       updatePersonBlock(data);
870     } else if (op == UserOperation.CreatePostReport) {
871       let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
872       if (data) {
873         toast(i18n.t("report_created"));
874       }
875     } else if (op == UserOperation.CreateCommentReport) {
876       let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
877       if (data) {
878         toast(i18n.t("report_created"));
879       }
880     } else if (op == UserOperation.CreatePrivateMessageReport) {
881       let data = wsJsonToRes<PrivateMessageReportResponse>(
882         msg,
883         PrivateMessageReportResponse
884       );
885       if (data) {
886         toast(i18n.t("report_created"));
887       }
888     }
889   }
890
891   isMention(view: any): view is PersonMentionView {
892     return (view as PersonMentionView).person_mention !== undefined;
893   }
894
895   isReply(view: any): view is CommentReplyView {
896     return (view as CommentReplyView).comment_reply !== undefined;
897   }
898 }