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