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