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