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