]> Untitled Git - lemmy-ui.git/blobdiff - src/shared/components/person/inbox.tsx
Changing all bigints to numbers
[lemmy-ui.git] / src / shared / components / person / inbox.tsx
index 10375874a4c6531359b665d44f7874b853d810f7..a39977917480aa13c8713abc0ff2fa8e12d65338 100644 (file)
@@ -1,46 +1,55 @@
 import { Component, linkEvent } from "inferno";
 import {
+  BlockPersonResponse,
+  CommentReplyResponse,
+  CommentReplyView,
+  CommentReportResponse,
   CommentResponse,
+  CommentSortType,
   CommentView,
   GetPersonMentions,
   GetPersonMentionsResponse,
   GetPrivateMessages,
   GetReplies,
   GetRepliesResponse,
+  GetSiteResponse,
   PersonMentionResponse,
   PersonMentionView,
+  PostReportResponse,
+  PrivateMessageReportResponse,
   PrivateMessageResponse,
-  PrivateMessagesResponse,
   PrivateMessageView,
-  SiteView,
-  SortType,
+  PrivateMessagesResponse,
   UserOperation,
+  wsJsonToRes,
+  wsUserOp,
 } from "lemmy-js-client";
 import { Subscription } from "rxjs";
 import { i18n } from "../../i18next";
-import { InitialFetchRequest } from "../../interfaces";
+import { CommentViewType, InitialFetchRequest } from "../../interfaces";
 import { UserService, WebSocketService } from "../../services";
 import {
-  authField,
   commentsToFlatNodes,
   createCommentLikeRes,
   editCommentRes,
+  enableDownvotes,
   fetchLimit,
   isBrowser,
+  myAuth,
+  relTags,
   saveCommentRes,
   setIsoData,
   setupTippy,
   toast,
+  updatePersonBlock,
   wsClient,
-  wsJsonToRes,
   wsSubscribe,
-  wsUserOp,
 } from "../../utils";
 import { CommentNodes } from "../comment/comment-nodes";
+import { CommentSortSelect } from "../common/comment-sort-select";
 import { HtmlTags } from "../common/html-tags";
 import { Icon, Spinner } from "../common/icon";
 import { Paginator } from "../common/paginator";
-import { SortSelect } from "../common/sort-select";
 import { PrivateMessage } from "../private_message/private-message";
 
 enum UnreadOrAll {
@@ -63,47 +72,46 @@ enum ReplyEnum {
 type ReplyType = {
   id: number;
   type_: ReplyEnum;
-  view: CommentView | PrivateMessageView | PersonMentionView;
+  view: CommentView | PrivateMessageView | PersonMentionView | CommentReplyView;
   published: string;
 };
 
 interface InboxState {
   unreadOrAll: UnreadOrAll;
   messageType: MessageType;
-  replies: CommentView[];
+  replies: CommentReplyView[];
   mentions: PersonMentionView[];
   messages: PrivateMessageView[];
   combined: ReplyType[];
-  sort: SortType;
+  sort: CommentSortType;
   page: number;
-  site_view: SiteView;
+  siteRes: GetSiteResponse;
   loading: boolean;
 }
 
 export class Inbox extends Component<any, InboxState> {
   private isoData = setIsoData(this.context);
-  private subscription: Subscription;
-  private emptyState: InboxState = {
+  private subscription?: Subscription;
+  state: InboxState = {
     unreadOrAll: UnreadOrAll.Unread,
     messageType: MessageType.All,
     replies: [],
     mentions: [],
     messages: [],
     combined: [],
-    sort: SortType.New,
+    sort: "New",
     page: 1,
-    site_view: this.isoData.site_res.site_view,
+    siteRes: this.isoData.site_res,
     loading: true,
   };
 
   constructor(props: any, context: any) {
     super(props, context);
 
-    this.state = this.emptyState;
     this.handleSortChange = this.handleSortChange.bind(this);
     this.handlePageChange = this.handlePageChange.bind(this);
 
-    if (!UserService.Instance.localUserView && isBrowser()) {
+    if (!UserService.Instance.myUserInfo && isBrowser()) {
       toast(i18n.t("not_logged_in"), "danger");
       this.context.router.history.push(`/login`);
     }
@@ -113,11 +121,19 @@ export class Inbox extends Component<any, InboxState> {
 
     // Only fetch the data if coming from another route
     if (this.isoData.path == this.context.router.route.match.url) {
-      this.state.replies = this.isoData.routeData[0].replies || [];
-      this.state.mentions = this.isoData.routeData[1].mentions || [];
-      this.state.messages = this.isoData.routeData[2].messages || [];
-      this.state.combined = this.buildCombined();
-      this.state.loading = false;
+      this.state = {
+        ...this.state,
+        replies:
+          (this.isoData.routeData[0] as GetRepliesResponse).replies || [],
+        mentions:
+          (this.isoData.routeData[1] as GetPersonMentionsResponse).mentions ||
+          [],
+        messages:
+          (this.isoData.routeData[2] as PrivateMessagesResponse)
+            .private_messages || [],
+        loading: false,
+      };
+      this.state = { ...this.state, combined: this.buildCombined() };
     } else {
       this.refetch();
     }
@@ -125,41 +141,49 @@ export class Inbox extends Component<any, InboxState> {
 
   componentWillUnmount() {
     if (isBrowser()) {
-      this.subscription.unsubscribe();
+      this.subscription?.unsubscribe();
     }
   }
 
   get documentTitle(): string {
-    return `@${UserService.Instance.localUserView.person.name} ${i18n.t(
-      "inbox"
-    )} - ${this.state.site_view.site.name}`;
+    let mui = UserService.Instance.myUserInfo;
+    return mui
+      ? `@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${
+          this.state.siteRes.site_view.site.name
+        }`
+      : "";
   }
 
   render() {
+    let auth = myAuth();
+    let inboxRss = auth ? `/feeds/inbox/${auth}.xml` : undefined;
     return (
-      <div class="container">
+      <div className="container-lg">
         {this.state.loading ? (
           <h5>
             <Spinner large />
           </h5>
         ) : (
-          <div class="row">
-            <div class="col-12">
+          <div className="row">
+            <div className="col-12">
               <HtmlTags
                 title={this.documentTitle}
                 path={this.context.router.route.match.url}
               />
-              <h5 class="mb-2">
+              <h5 className="mb-2">
                 {i18n.t("inbox")}
-                <small>
-                  <a
-                    href={`/feeds/inbox/${UserService.Instance.auth}.xml`}
-                    title="RSS"
-                    rel="noopener"
-                  >
-                    <Icon icon="rss" classes="ml-2 text-muted small" />
-                  </a>
-                </small>
+                {inboxRss && (
+                  <small>
+                    <a href={inboxRss} title="RSS" rel={relTags}>
+                      <Icon icon="rss" classes="ml-2 text-muted small" />
+                    </a>
+                    <link
+                      rel="alternate"
+                      type="application/atom+xml"
+                      href={inboxRss}
+                    />
+                  </small>
+                )}
               </h5>
               {this.state.replies.length +
                 this.state.mentions.length +
@@ -167,7 +191,7 @@ export class Inbox extends Component<any, InboxState> {
                 0 &&
                 this.state.unreadOrAll == UnreadOrAll.Unread && (
                   <button
-                    class="btn btn-secondary mb-2"
+                    className="btn btn-secondary mb-2"
                     onClick={linkEvent(this, this.markAllAsRead)}
                   >
                     {i18n.t("mark_all_as_read")}
@@ -193,7 +217,7 @@ export class Inbox extends Component<any, InboxState> {
 
   unreadOrAllRadios() {
     return (
-      <div class="btn-group btn-group-toggle flex-wrap mb-2">
+      <div className="btn-group btn-group-toggle flex-wrap mb-2">
         <label
           className={`btn btn-outline-secondary pointer
             ${this.state.unreadOrAll == UnreadOrAll.Unread && "active"}
@@ -226,7 +250,7 @@ export class Inbox extends Component<any, InboxState> {
 
   messageTypeRadios() {
     return (
-      <div class="btn-group btn-group-toggle flex-wrap mb-2">
+      <div className="btn-group btn-group-toggle flex-wrap mb-2">
         <label
           className={`btn btn-outline-secondary pointer
             ${this.state.messageType == MessageType.All && "active"}
@@ -286,21 +310,19 @@ export class Inbox extends Component<any, InboxState> {
   selects() {
     return (
       <div className="mb-2">
-        <span class="mr-3">{this.unreadOrAllRadios()}</span>
-        <span class="mr-3">{this.messageTypeRadios()}</span>
-        <SortSelect
+        <span className="mr-3">{this.unreadOrAllRadios()}</span>
+        <span className="mr-3">{this.messageTypeRadios()}</span>
+        <CommentSortSelect
           sort={this.state.sort}
           onChange={this.handleSortChange}
-          hideHot
-          hideMostComments
         />
       </div>
     );
   }
 
-  replyToReplyType(r: CommentView): ReplyType {
+  replyToReplyType(r: CommentReplyView): ReplyType {
     return {
-      id: r.comment.id,
+      id: r.comment_reply.id,
       type_: ReplyEnum.Reply,
       view: r,
       published: r.comment.published,
@@ -347,24 +369,38 @@ export class Inbox extends Component<any, InboxState> {
         return (
           <CommentNodes
             key={i.id}
-            nodes={[{ comment_view: i.view as CommentView }]}
+            nodes={[
+              { comment_view: i.view as CommentView, children: [], depth: 0 },
+            ]}
+            viewType={CommentViewType.Flat}
             noIndent
             markable
             showCommunity
             showContext
-            enableDownvotes={this.state.site_view.site.enable_downvotes}
+            enableDownvotes={enableDownvotes(this.state.siteRes)}
+            allLanguages={this.state.siteRes.all_languages}
+            siteLanguages={this.state.siteRes.discussion_languages}
           />
         );
       case ReplyEnum.Mention:
         return (
           <CommentNodes
             key={i.id}
-            nodes={[{ comment_view: i.view as PersonMentionView }]}
+            nodes={[
+              {
+                comment_view: i.view as PersonMentionView,
+                children: [],
+                depth: 0,
+              },
+            ]}
+            viewType={CommentViewType.Flat}
             noIndent
             markable
             showCommunity
             showContext
-            enableDownvotes={this.state.site_view.site.enable_downvotes}
+            enableDownvotes={enableDownvotes(this.state.siteRes)}
+            allLanguages={this.state.siteRes.all_languages}
+            siteLanguages={this.state.siteRes.discussion_languages}
           />
         );
       case ReplyEnum.Message:
@@ -388,11 +424,14 @@ export class Inbox extends Component<any, InboxState> {
       <div>
         <CommentNodes
           nodes={commentsToFlatNodes(this.state.replies)}
+          viewType={CommentViewType.Flat}
           noIndent
           markable
           showCommunity
           showContext
-          enableDownvotes={this.state.site_view.site.enable_downvotes}
+          enableDownvotes={enableDownvotes(this.state.siteRes)}
+          allLanguages={this.state.siteRes.all_languages}
+          siteLanguages={this.state.siteRes.discussion_languages}
         />
       </div>
     );
@@ -404,12 +443,15 @@ export class Inbox extends Component<any, InboxState> {
         {this.state.mentions.map(umv => (
           <CommentNodes
             key={umv.person_mention.id}
-            nodes={[{ comment_view: umv }]}
+            nodes={[{ comment_view: umv, children: [], depth: 0 }]}
+            viewType={CommentViewType.Flat}
             noIndent
             markable
             showCommunity
             showContext
-            enableDownvotes={this.state.site_view.site.enable_downvotes}
+            enableDownvotes={enableDownvotes(this.state.siteRes)}
+            allLanguages={this.state.siteRes.all_languages}
+            siteLanguages={this.state.siteRes.discussion_languages}
           />
         ))}
       </div>
@@ -435,103 +477,121 @@ export class Inbox extends Component<any, InboxState> {
   }
 
   handleUnreadOrAllChange(i: Inbox, event: any) {
-    i.state.unreadOrAll = Number(event.target.value);
-    i.state.page = 1;
-    i.setState(i.state);
+    i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
     i.refetch();
   }
 
   handleMessageTypeChange(i: Inbox, event: any) {
-    i.state.messageType = Number(event.target.value);
-    i.state.page = 1;
-    i.setState(i.state);
+    i.setState({ messageType: Number(event.target.value), page: 1 });
     i.refetch();
   }
 
   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
     let promises: Promise<any>[] = [];
 
-    // It can be /u/me, or /username/1
-    let repliesForm: GetReplies = {
-      sort: SortType.New,
-      unread_only: true,
-      page: 1,
-      limit: fetchLimit,
-      auth: req.auth,
-    };
-    promises.push(req.client.getReplies(repliesForm));
-
-    let personMentionsForm: GetPersonMentions = {
-      sort: SortType.New,
-      unread_only: true,
-      page: 1,
-      limit: fetchLimit,
-      auth: req.auth,
-    };
-    promises.push(req.client.getPersonMentions(personMentionsForm));
-
-    let privateMessagesForm: GetPrivateMessages = {
-      unread_only: true,
-      page: 1,
-      limit: fetchLimit,
-      auth: req.auth,
-    };
-    promises.push(req.client.getPrivateMessages(privateMessagesForm));
+    let sort: CommentSortType = "New";
+    let auth = req.auth;
+
+    if (auth) {
+      // It can be /u/me, or /username/1
+      let repliesForm: GetReplies = {
+        sort: "New",
+        unread_only: true,
+        page: 1,
+        limit: fetchLimit,
+        auth,
+      };
+      promises.push(req.client.getReplies(repliesForm));
+
+      let personMentionsForm: GetPersonMentions = {
+        sort,
+        unread_only: true,
+        page: 1,
+        limit: fetchLimit,
+        auth,
+      };
+      promises.push(req.client.getPersonMentions(personMentionsForm));
+
+      let privateMessagesForm: GetPrivateMessages = {
+        unread_only: true,
+        page: 1,
+        limit: fetchLimit,
+        auth,
+      };
+      promises.push(req.client.getPrivateMessages(privateMessagesForm));
+    }
 
     return promises;
   }
 
   refetch() {
-    let repliesForm: GetReplies = {
-      sort: this.state.sort,
-      unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
-      page: this.state.page,
-      limit: fetchLimit,
-      auth: authField(),
-    };
-    WebSocketService.Instance.send(wsClient.getReplies(repliesForm));
-
-    let personMentionsForm: GetPersonMentions = {
-      sort: this.state.sort,
-      unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
-      page: this.state.page,
-      limit: fetchLimit,
-      auth: authField(),
-    };
-    WebSocketService.Instance.send(
-      wsClient.getPersonMentions(personMentionsForm)
-    );
+    let sort = this.state.sort;
+    let unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
+    let page = this.state.page;
+    let limit = fetchLimit;
+    let auth = myAuth();
+
+    if (auth) {
+      let repliesForm: GetReplies = {
+        sort,
+        unread_only,
+        page,
+        limit,
+        auth,
+      };
+      WebSocketService.Instance.send(wsClient.getReplies(repliesForm));
+
+      let personMentionsForm: GetPersonMentions = {
+        sort,
+        unread_only,
+        page,
+        limit,
+        auth,
+      };
+      WebSocketService.Instance.send(
+        wsClient.getPersonMentions(personMentionsForm)
+      );
 
-    let privateMessagesForm: GetPrivateMessages = {
-      unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
-      page: this.state.page,
-      limit: fetchLimit,
-      auth: authField(),
-    };
-    WebSocketService.Instance.send(
-      wsClient.getPrivateMessages(privateMessagesForm)
-    );
+      let privateMessagesForm: GetPrivateMessages = {
+        unread_only,
+        page,
+        limit,
+        auth,
+      };
+      WebSocketService.Instance.send(
+        wsClient.getPrivateMessages(privateMessagesForm)
+      );
+    }
   }
 
-  handleSortChange(val: SortType) {
-    this.state.sort = val;
-    this.state.page = 1;
-    this.setState(this.state);
+  handleSortChange(val: CommentSortType) {
+    this.setState({ sort: val, page: 1 });
     this.refetch();
   }
 
   markAllAsRead(i: Inbox) {
-    WebSocketService.Instance.send(
-      wsClient.markAllAsRead({
-        auth: authField(),
-      })
-    );
-    i.state.replies = [];
-    i.state.mentions = [];
-    i.state.messages = [];
-    i.sendUnreadCount();
-    window.scrollTo(0, 0);
-    i.setState(i.state);
+    let auth = myAuth();
+    if (auth) {
+      WebSocketService.Instance.send(
+        wsClient.markAllAsRead({
+          auth,
+        })
+      );
+      i.setState({ replies: [], mentions: [], messages: [] });
+      i.setState({ combined: i.buildCombined() });
+      UserService.Instance.unreadInboxCountSub.next(0);
+      window.scrollTo(0, 0);
+      i.setState(i.state);
+    }
+  }
+
+  sendUnreadCount(read: boolean) {
+    let urcs = UserService.Instance.unreadInboxCountSub;
+    if (read) {
+      urcs.next(urcs.getValue() - 1);
+    } else {
+      urcs.next(urcs.getValue() + 1);
+    }
   }
 
   parseMessage(msg: any) {
@@ -543,95 +603,100 @@ export class Inbox extends Component<any, InboxState> {
     } else if (msg.reconnect) {
       this.refetch();
     } else if (op == UserOperation.GetReplies) {
-      let data = wsJsonToRes<GetRepliesResponse>(msg).data;
-      this.state.replies = data.replies;
-      this.state.combined = this.buildCombined();
-      this.state.loading = false;
-      this.sendUnreadCount();
+      let data = wsJsonToRes<GetRepliesResponse>(msg);
+      this.setState({ replies: data.replies });
+      this.setState({ combined: this.buildCombined(), loading: false });
       window.scrollTo(0, 0);
-      this.setState(this.state);
       setupTippy();
     } else if (op == UserOperation.GetPersonMentions) {
-      let data = wsJsonToRes<GetPersonMentionsResponse>(msg).data;
-      this.state.mentions = data.mentions;
-      this.state.combined = this.buildCombined();
-      this.sendUnreadCount();
+      let data = wsJsonToRes<GetPersonMentionsResponse>(msg);
+      this.setState({ mentions: data.mentions });
+      this.setState({ combined: this.buildCombined() });
       window.scrollTo(0, 0);
-      this.setState(this.state);
       setupTippy();
     } else if (op == UserOperation.GetPrivateMessages) {
-      let data = wsJsonToRes<PrivateMessagesResponse>(msg).data;
-      this.state.messages = data.private_messages;
-      this.state.combined = this.buildCombined();
-      this.sendUnreadCount();
+      let data = wsJsonToRes<PrivateMessagesResponse>(msg);
+      this.setState({ messages: data.private_messages });
+      this.setState({ combined: this.buildCombined() });
       window.scrollTo(0, 0);
-      this.setState(this.state);
       setupTippy();
     } else if (op == UserOperation.EditPrivateMessage) {
-      let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
-      let found: PrivateMessageView = this.state.messages.find(
+      let data = wsJsonToRes<PrivateMessageResponse>(msg);
+      let found = this.state.messages.find(
         m =>
           m.private_message.id === data.private_message_view.private_message.id
       );
       if (found) {
         let combinedView = this.state.combined.find(
           i => i.id == data.private_message_view.private_message.id
-        ).view as PrivateMessageView;
-        found.private_message.content = combinedView.private_message.content =
-          data.private_message_view.private_message.content;
-        found.private_message.updated = combinedView.private_message.updated =
-          data.private_message_view.private_message.updated;
+        )?.view as PrivateMessageView | undefined;
+        if (combinedView) {
+          found.private_message.content = combinedView.private_message.content =
+            data.private_message_view.private_message.content;
+          found.private_message.updated = combinedView.private_message.updated =
+            data.private_message_view.private_message.updated;
+        }
       }
       this.setState(this.state);
     } else if (op == UserOperation.DeletePrivateMessage) {
-      let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
-      let found: PrivateMessageView = this.state.messages.find(
+      let data = wsJsonToRes<PrivateMessageResponse>(msg);
+      let found = this.state.messages.find(
         m =>
           m.private_message.id === data.private_message_view.private_message.id
       );
       if (found) {
         let combinedView = this.state.combined.find(
           i => i.id == data.private_message_view.private_message.id
-        ).view as PrivateMessageView;
-        found.private_message.deleted = combinedView.private_message.deleted =
-          data.private_message_view.private_message.deleted;
-        found.private_message.updated = combinedView.private_message.updated =
-          data.private_message_view.private_message.updated;
+        )?.view as PrivateMessageView | undefined;
+        if (combinedView) {
+          found.private_message.deleted = combinedView.private_message.deleted =
+            data.private_message_view.private_message.deleted;
+          found.private_message.updated = combinedView.private_message.updated =
+            data.private_message_view.private_message.updated;
+        }
       }
       this.setState(this.state);
     } else if (op == UserOperation.MarkPrivateMessageAsRead) {
-      let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
-      let found: PrivateMessageView = this.state.messages.find(
+      let data = wsJsonToRes<PrivateMessageResponse>(msg);
+      let found = this.state.messages.find(
         m =>
           m.private_message.id === data.private_message_view.private_message.id
       );
 
       if (found) {
         let combinedView = this.state.combined.find(
-          i => i.id == data.private_message_view.private_message.id
-        ).view as PrivateMessageView;
-        found.private_message.updated = combinedView.private_message.updated =
-          data.private_message_view.private_message.updated;
-
-        // If youre in the unread view, just remove it from the list
-        if (
-          this.state.unreadOrAll == UnreadOrAll.Unread &&
-          data.private_message_view.private_message.read
-        ) {
-          this.state.messages = this.state.messages.filter(
-            r =>
-              r.private_message.id !==
-              data.private_message_view.private_message.id
-          );
-          this.state.combined = this.state.combined.filter(
-            r => r.id !== data.private_message_view.private_message.id
-          );
-        } else {
-          found.private_message.read = combinedView.private_message.read =
-            data.private_message_view.private_message.read;
+          i =>
+            i.id == data.private_message_view.private_message.id &&
+            i.type_ == ReplyEnum.Message
+        )?.view as PrivateMessageView | undefined;
+        if (combinedView) {
+          found.private_message.updated = combinedView.private_message.updated =
+            data.private_message_view.private_message.updated;
+
+          // If youre in the unread view, just remove it from the list
+          if (
+            this.state.unreadOrAll == UnreadOrAll.Unread &&
+            data.private_message_view.private_message.read
+          ) {
+            this.setState({
+              messages: this.state.messages.filter(
+                r =>
+                  r.private_message.id !==
+                  data.private_message_view.private_message.id
+              ),
+            });
+            this.setState({
+              combined: this.state.combined.filter(
+                r => r.id !== data.private_message_view.private_message.id
+              ),
+            });
+          } else {
+            found.private_message.read = combinedView.private_message.read =
+              data.private_message_view.private_message.read;
+          }
         }
       }
-      this.sendUnreadCount();
+      this.sendUnreadCount(data.private_message_view.private_message.read);
       this.setState(this.state);
     } else if (op == UserOperation.MarkAllAsRead) {
       // Moved to be instant
@@ -640,38 +705,65 @@ export class Inbox extends Component<any, InboxState> {
       op == UserOperation.DeleteComment ||
       op == UserOperation.RemoveComment
     ) {
-      let data = wsJsonToRes<CommentResponse>(msg).data;
+      let data = wsJsonToRes<CommentResponse>(msg);
       editCommentRes(data.comment_view, this.state.replies);
       this.setState(this.state);
-    } else if (op == UserOperation.MarkCommentAsRead) {
-      let data = wsJsonToRes<CommentResponse>(msg).data;
+    } else if (op == UserOperation.MarkCommentReplyAsRead) {
+      let data = wsJsonToRes<CommentReplyResponse>(msg);
 
-      // If youre in the unread view, just remove it from the list
-      if (
-        this.state.unreadOrAll == UnreadOrAll.Unread &&
-        data.comment_view.comment.read
-      ) {
-        this.state.replies = this.state.replies.filter(
-          r => r.comment.id !== data.comment_view.comment.id
-        );
-        this.state.combined = this.state.combined.filter(
-          r => r.id !== data.comment_view.comment.id
-        );
-      } else {
-        let found = this.state.replies.find(
-          c => c.comment.id == data.comment_view.comment.id
-        );
+      let found = this.state.replies.find(
+        c => c.comment_reply.id == data.comment_reply_view.comment_reply.id
+      );
+
+      if (found) {
         let combinedView = this.state.combined.find(
-          i => i.id == data.comment_view.comment.id
-        ).view as CommentView;
-        found.comment.read = combinedView.comment.read =
-          data.comment_view.comment.read;
+          i =>
+            i.id == data.comment_reply_view.comment_reply.id &&
+            i.type_ == ReplyEnum.Reply
+        )?.view as CommentReplyView | undefined;
+        if (combinedView) {
+          found.comment.content = combinedView.comment.content =
+            data.comment_reply_view.comment.content;
+          found.comment.updated = combinedView.comment.updated =
+            data.comment_reply_view.comment.updated;
+          found.comment.removed = combinedView.comment.removed =
+            data.comment_reply_view.comment.removed;
+          found.comment.deleted = combinedView.comment.deleted =
+            data.comment_reply_view.comment.deleted;
+          found.counts.upvotes = combinedView.counts.upvotes =
+            data.comment_reply_view.counts.upvotes;
+          found.counts.downvotes = combinedView.counts.downvotes =
+            data.comment_reply_view.counts.downvotes;
+          found.counts.score = combinedView.counts.score =
+            data.comment_reply_view.counts.score;
+
+          // If youre in the unread view, just remove it from the list
+          if (
+            this.state.unreadOrAll == UnreadOrAll.Unread &&
+            data.comment_reply_view.comment_reply.read
+          ) {
+            this.setState({
+              replies: this.state.replies.filter(
+                r =>
+                  r.comment_reply.id !==
+                  data.comment_reply_view.comment_reply.id
+              ),
+            });
+            this.setState({
+              combined: this.state.combined.filter(
+                r => r.id !== data.comment_reply_view.comment_reply.id
+              ),
+            });
+          } else {
+            found.comment_reply.read = combinedView.comment_reply.read =
+              data.comment_reply_view.comment_reply.read;
+          }
+        }
       }
-      this.sendUnreadCount();
+      this.sendUnreadCount(data.comment_reply_view.comment_reply.read);
       this.setState(this.state);
-      setupTippy();
     } else if (op == UserOperation.MarkPersonMentionAsRead) {
-      let data = wsJsonToRes<PersonMentionResponse>(msg).data;
+      let data = wsJsonToRes<PersonMentionResponse>(msg);
 
       // TODO this might not be correct, it might need to use the comment id
       let found = this.state.mentions.find(
@@ -680,66 +772,57 @@ export class Inbox extends Component<any, InboxState> {
 
       if (found) {
         let combinedView = this.state.combined.find(
-          i => i.id == data.person_mention_view.person_mention.id
-        ).view as PersonMentionView;
-        found.comment.content = combinedView.comment.content =
-          data.person_mention_view.comment.content;
-        found.comment.updated = combinedView.comment.updated =
-          data.person_mention_view.comment.updated;
-        found.comment.removed = combinedView.comment.removed =
-          data.person_mention_view.comment.removed;
-        found.comment.deleted = combinedView.comment.deleted =
-          data.person_mention_view.comment.deleted;
-        found.counts.upvotes = combinedView.counts.upvotes =
-          data.person_mention_view.counts.upvotes;
-        found.counts.downvotes = combinedView.counts.downvotes =
-          data.person_mention_view.counts.downvotes;
-        found.counts.score = combinedView.counts.score =
-          data.person_mention_view.counts.score;
-
-        // If youre in the unread view, just remove it from the list
-        if (
-          this.state.unreadOrAll == UnreadOrAll.Unread &&
-          data.person_mention_view.person_mention.read
-        ) {
-          this.state.mentions = this.state.mentions.filter(
-            r =>
-              r.person_mention.id !== data.person_mention_view.person_mention.id
-          );
-          this.state.combined = this.state.combined.filter(
-            r => r.id !== data.person_mention_view.person_mention.id
-          );
-        } else {
-          // TODO test to make sure these mentions are getting marked as read
-          found.person_mention.read = combinedView.person_mention.read =
-            data.person_mention_view.person_mention.read;
+          i =>
+            i.id == data.person_mention_view.person_mention.id &&
+            i.type_ == ReplyEnum.Mention
+        )?.view as PersonMentionView | undefined;
+        if (combinedView) {
+          found.comment.content = combinedView.comment.content =
+            data.person_mention_view.comment.content;
+          found.comment.updated = combinedView.comment.updated =
+            data.person_mention_view.comment.updated;
+          found.comment.removed = combinedView.comment.removed =
+            data.person_mention_view.comment.removed;
+          found.comment.deleted = combinedView.comment.deleted =
+            data.person_mention_view.comment.deleted;
+          found.counts.upvotes = combinedView.counts.upvotes =
+            data.person_mention_view.counts.upvotes;
+          found.counts.downvotes = combinedView.counts.downvotes =
+            data.person_mention_view.counts.downvotes;
+          found.counts.score = combinedView.counts.score =
+            data.person_mention_view.counts.score;
+
+          // If youre in the unread view, just remove it from the list
+          if (
+            this.state.unreadOrAll == UnreadOrAll.Unread &&
+            data.person_mention_view.person_mention.read
+          ) {
+            this.setState({
+              mentions: this.state.mentions.filter(
+                r =>
+                  r.person_mention.id !==
+                  data.person_mention_view.person_mention.id
+              ),
+            });
+            this.setState({
+              combined: this.state.combined.filter(
+                r => r.id !== data.person_mention_view.person_mention.id
+              ),
+            });
+          } else {
+            // TODO test to make sure these mentions are getting marked as read
+            found.person_mention.read = combinedView.person_mention.read =
+              data.person_mention_view.person_mention.read;
+          }
         }
       }
-      this.sendUnreadCount();
+      this.sendUnreadCount(data.person_mention_view.person_mention.read);
       this.setState(this.state);
-    } else if (op == UserOperation.CreateComment) {
-      let data = wsJsonToRes<CommentResponse>(msg).data;
-
-      if (
-        data.recipient_ids.includes(
-          UserService.Instance.localUserView.local_user.id
-        )
-      ) {
-        this.state.replies.unshift(data.comment_view);
-        this.state.combined.unshift(this.replyToReplyType(data.comment_view));
-        this.setState(this.state);
-      } else if (
-        data.comment_view.creator.id ==
-        UserService.Instance.localUserView.person.id
-      ) {
-        // TODO this seems wrong, you should be using form_id
-        toast(i18n.t("reply_sent"));
-      }
     } else if (op == UserOperation.CreatePrivateMessage) {
-      let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
+      let data = wsJsonToRes<PrivateMessageResponse>(msg);
+      let mui = UserService.Instance.myUserInfo;
       if (
-        data.private_message_view.recipient.id ==
-        UserService.Instance.localUserView.person.id
+        data.private_message_view.recipient.id == mui?.local_user_view.person.id
       ) {
         this.state.messages.unshift(data.private_message_view);
         this.state.combined.unshift(
@@ -748,32 +831,40 @@ export class Inbox extends Component<any, InboxState> {
         this.setState(this.state);
       }
     } else if (op == UserOperation.SaveComment) {
-      let data = wsJsonToRes<CommentResponse>(msg).data;
+      let data = wsJsonToRes<CommentResponse>(msg);
       saveCommentRes(data.comment_view, this.state.replies);
       this.setState(this.state);
       setupTippy();
     } else if (op == UserOperation.CreateCommentLike) {
-      let data = wsJsonToRes<CommentResponse>(msg).data;
+      let data = wsJsonToRes<CommentResponse>(msg);
       createCommentLikeRes(data.comment_view, this.state.replies);
       this.setState(this.state);
+    } else if (op == UserOperation.BlockPerson) {
+      let data = wsJsonToRes<BlockPersonResponse>(msg);
+      updatePersonBlock(data);
+    } else if (op == UserOperation.CreatePostReport) {
+      let data = wsJsonToRes<PostReportResponse>(msg);
+      if (data) {
+        toast(i18n.t("report_created"));
+      }
+    } else if (op == UserOperation.CreateCommentReport) {
+      let data = wsJsonToRes<CommentReportResponse>(msg);
+      if (data) {
+        toast(i18n.t("report_created"));
+      }
+    } else if (op == UserOperation.CreatePrivateMessageReport) {
+      let data = wsJsonToRes<PrivateMessageReportResponse>(msg);
+      if (data) {
+        toast(i18n.t("report_created"));
+      }
     }
   }
 
-  sendUnreadCount() {
-    UserService.Instance.unreadCountSub.next(this.unreadCount());
+  isMention(view: any): view is PersonMentionView {
+    return (view as PersonMentionView).person_mention !== undefined;
   }
 
-  unreadCount(): number {
-    return (
-      this.state.replies.filter(r => !r.comment.read).length +
-      this.state.mentions.filter(r => !r.person_mention.read).length +
-      this.state.messages.filter(
-        r =>
-          UserService.Instance.localUserView &&
-          !r.private_message.read &&
-          // TODO also seems very strange and wrong
-          r.creator.id !== UserService.Instance.localUserView.person.id
-      ).length
-    );
+  isReply(view: any): view is CommentReplyView {
+    return (view as CommentReplyView).comment_reply !== undefined;
   }
 }