]> Untitled Git - lemmy.git/commitdiff
Mostly working, before merge
authorDessalines <tyhou13@gmx.com>
Sat, 20 Apr 2019 18:17:00 +0000 (11:17 -0700)
committerDessalines <tyhou13@gmx.com>
Sat, 20 Apr 2019 18:17:00 +0000 (11:17 -0700)
16 files changed:
server/migrations/2019-04-03-155309_create_comment_view/down.sql
server/migrations/2019-04-03-155309_create_comment_view/up.sql
server/src/actions/comment.rs
server/src/actions/comment_view.rs
server/src/websocket_server/server.rs
ui/src/components/comment-node.tsx
ui/src/components/comment-nodes.tsx
ui/src/components/inbox.tsx [new file with mode: 0644]
ui/src/components/main.tsx
ui/src/components/navbar.tsx
ui/src/components/post.tsx
ui/src/css/main.css
ui/src/index.tsx
ui/src/interfaces.ts
ui/src/services/UserService.ts
ui/src/services/WebSocketService.ts

index 2da934a4822fc4c62796d3116d2998cfc8595cfd..c19d5ff7e01ffa008b113533837c85fd04fee4e8 100644 (file)
@@ -1 +1,2 @@
+drop view reply_view;
 drop view comment_view;
index a78e3ac340fdf6ba2c51adedb3b4879512fb2d85..24ce98fcc70be6333b53ecf6803c5540e2942aee 100644 (file)
@@ -33,3 +33,28 @@ select
     null as saved
 from all_comment ac
 ;
+
+create view reply_view as 
+with closereply as (
+    select 
+    c2.id, 
+    c2.creator_id as sender_id, 
+    c.creator_id as recipient_id
+    from comment c
+    inner join comment c2 on c.id = c2.parent_id
+    where c2.creator_id != c.creator_id
+    -- Do union where post is null
+    union
+    select
+    c.id,
+    c.creator_id as sender_id,
+    p.creator_id as recipient_id
+    from comment c, post p
+    where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
+)
+select cv.*,
+closereply.recipient_id
+from comment_view cv, closereply
+where closereply.id = cv.id
+;
+
index 36da90c65d2c4acb8aedc9b1ba793633fadd1fb3..9bb6d018f4cfe9e0b48588018ca426926ada28a6 100644 (file)
@@ -36,6 +36,7 @@ pub struct CommentForm {
   pub parent_id: Option<i32>,
   pub content: String,
   pub removed: Option<bool>,
+  pub read: Option<bool>,
   pub updated: Option<chrono::NaiveDateTime>
 }
 
@@ -208,6 +209,7 @@ mod tests {
       creator_id: inserted_user.id,
       post_id: inserted_post.id,
       removed: None,
+      read: None,
       parent_id: None,
       updated: None
     };
@@ -232,6 +234,7 @@ mod tests {
       post_id: inserted_post.id,
       parent_id: Some(inserted_comment.id),
       removed: None,
+      read: None,
       updated: None
     };
 
index 4e3d99b7e74202f6ff983f1d7af5efc5cfed0045..e8b96e3ae7a24c58b4a6a69a16d1729eac96b002 100644 (file)
@@ -136,6 +136,107 @@ impl CommentView {
 }
 
 
+// The faked schema since diesel doesn't do views
+table! {
+  reply_view (id) {
+    id -> Int4,
+    creator_id -> Int4,
+    post_id -> Int4,
+    parent_id -> Nullable<Int4>,
+    content -> Text,
+    removed -> Bool,
+    read -> Bool,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+    community_id -> Int4,
+    banned -> Bool,
+    banned_from_community -> Bool,
+    creator_name -> Varchar,
+    score -> BigInt,
+    upvotes -> BigInt,
+    downvotes -> BigInt,
+    user_id -> Nullable<Int4>,
+    my_vote -> Nullable<Int4>,
+    saved -> Nullable<Bool>,
+    recipient_id -> Int4,
+  }
+}
+
+#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
+#[table_name="reply_view"]
+pub struct ReplyView {
+  pub id: i32,
+  pub creator_id: i32,
+  pub post_id: i32,
+  pub parent_id: Option<i32>,
+  pub content: String,
+  pub removed: bool,
+  pub read: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub community_id: i32,
+  pub banned: bool,
+  pub banned_from_community: bool,
+  pub creator_name: String,
+  pub score: i64,
+  pub upvotes: i64,
+  pub downvotes: i64,
+  pub user_id: Option<i32>,
+  pub my_vote: Option<i32>,
+  pub saved: Option<bool>,
+  pub recipient_id: i32,
+}
+
+impl ReplyView {
+
+  pub fn get_replies(conn: &PgConnection, 
+              for_user_id: i32, 
+              sort: &SortType, 
+              unread_only: bool,
+              page: Option<i64>,
+              limit: Option<i64>,
+              ) -> Result<Vec<Self>, Error> {
+    use actions::comment_view::reply_view::dsl::*;
+
+    let (limit, offset) = limit_and_offset(page, limit);
+
+    let mut query = reply_view.into_boxed();
+
+    query = query
+      .filter(user_id.eq(for_user_id))
+      .filter(recipient_id.eq(for_user_id));
+
+    if unread_only {
+      query = query.filter(read.eq(false));
+    }
+
+    query = match sort {
+      // SortType::Hot => query.order_by(hot_rank.desc()),
+      SortType::New => query.order_by(published.desc()),
+      SortType::TopAll => query.order_by(score.desc()),
+      SortType::TopYear => query
+        .filter(published.gt(now - 1.years()))
+        .order_by(score.desc()),
+        SortType::TopMonth => query
+          .filter(published.gt(now - 1.months()))
+          .order_by(score.desc()),
+          SortType::TopWeek => query
+            .filter(published.gt(now - 1.weeks()))
+            .order_by(score.desc()),
+            SortType::TopDay => query
+              .filter(published.gt(now - 1.days()))
+              .order_by(score.desc()),
+              _ => query.order_by(published.desc())
+    };
+
+    query
+      .limit(limit)
+      .offset(offset)
+      .load::<Self>(conn) 
+  }
+
+}
+
 #[cfg(test)]
 mod tests {
   use establish_connection;
index 63d767c24464d43ba093c1ca693916b0f9cf96ec..dbcf5c5d8b9c542fc6a32473bc7e43d7f99a3599 100644 (file)
@@ -26,7 +26,7 @@ use actions::moderator::*;
 
 #[derive(EnumString,ToString,Debug)]
 pub enum UserOperation {
-  Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser
+  Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser
 }
 
 #[derive(Serialize, Deserialize)]
@@ -215,6 +215,7 @@ pub struct EditComment {
   post_id: i32,
   removed: Option<bool>,
   reason: Option<String>,
+  read: Option<bool>,
   auth: String
 }
 
@@ -439,6 +440,21 @@ pub struct BanUserResponse {
   banned: bool,
 }
 
+#[derive(Serialize, Deserialize)]
+pub struct GetReplies {
+  sort: String,
+  page: Option<i64>,
+  limit: Option<i64>,
+  unread_only: bool,
+  auth: String
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct GetRepliesResponse {
+  op: String,
+  replies: Vec<ReplyView>,
+}
+
 /// `ChatServer` manages chat rooms and responsible for coordinating chat
 /// session. implementation is super primitive
 pub struct ChatServer {
@@ -671,6 +687,10 @@ impl Handler<StandardMessage> for ChatServer {
         let ban_user: BanUser = serde_json::from_str(data).unwrap();
         ban_user.perform(self, msg.id)
       },
+      UserOperation::GetReplies => {
+        let get_replies: GetReplies = serde_json::from_str(data).unwrap();
+        get_replies.perform(self, msg.id)
+      },
     };
 
     MessageResult(res)
@@ -1181,6 +1201,7 @@ impl Perform for CreateComment {
       post_id: self.post_id,
       creator_id: user_id,
       removed: None,
+      read: None,
       updated: None
     };
 
@@ -1292,6 +1313,7 @@ impl Perform for EditComment {
       post_id: self.post_id,
       creator_id: self.creator_id,
       removed: self.removed.to_owned(),
+      read: self.read.to_owned(),
       updated: Some(naive_now())
     };
 
@@ -2027,6 +2049,39 @@ impl Perform for GetModlog {
   }
 }
 
+impl Perform for GetReplies {
+  fn op_type(&self) -> UserOperation {
+    UserOperation::GetReplies
+  }
+
+  fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String {
+
+    let conn = establish_connection();
+
+    let claims = match Claims::decode(&self.auth) {
+      Ok(claims) => claims.claims,
+      Err(_e) => {
+        return self.error("Not logged in.");
+      }
+    };
+
+    let user_id = claims.id;
+
+    let sort = SortType::from_str(&self.sort).expect("listing sort");
+
+    let replies = ReplyView::get_replies(&conn, user_id, &sort, self.unread_only, self.page, self.limit).unwrap();
+
+    // Return the jwt
+    serde_json::to_string(
+      &GetRepliesResponse {
+        op: self.op_type().to_string(),
+        replies: replies,
+      }
+      )
+      .unwrap()
+  }
+}
+
 impl Perform for BanFromCommunity {
   fn op_type(&self) -> UserOperation {
     UserOperation::BanFromCommunity
index 90cf5a54eb5c1e4204f10969597f5d7e05cb14c0..cf7b1bceadd5ecb61d2abad6346d5708ccf826db 100644 (file)
@@ -25,6 +25,7 @@ interface CommentNodeProps {
   noIndent?: boolean;
   viewOnly?: boolean;
   locked?: boolean;
+  markable?: boolean;
   moderators: Array<CommunityUser>;
   admins: Array<UserView>;
 }
@@ -146,7 +147,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                         }
                         {!this.props.node.comment.banned &&
                           <li className="list-inline-item">
-                            <span class="pointer" onClick={linkEvent(this, this.addAdmin)}>{`${this.isAdmin ? 'remove' : 'appoint'} as admin`}</span>
+                            <span class="pointer" onClick={linkEvent(this, this.handleAddAdmin)}>{`${this.isAdmin ? 'remove' : 'appoint'} as admin`}</span>
                           </li>
                         }
                       </>
@@ -156,6 +157,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                 <li className="list-inline-item">
                   <Link className="text-muted" to={`/post/${node.comment.post_id}/comment/${node.comment.id}`} target="_blank">link</Link>
                 </li>
+                {this.props.markable && 
+                  <li className="list-inline-item">
+                    <span class="pointer" onClick={linkEvent(this, this.handleMarkRead)}>{`mark as ${node.comment.read ? 'unread' : 'read'}`}</span>
+                  </li>
+                }
               </ul>
             </div>
           }
@@ -309,6 +315,20 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     i.setState(i.state);
   }
 
+  handleMarkRead(i: CommentNode) {
+    let form: CommentFormI = {
+      content: i.props.node.comment.content,
+      edit_id: i.props.node.comment.id,
+      creator_id: i.props.node.comment.creator_id,
+      post_id: i.props.node.comment.post_id,
+      parent_id: i.props.node.comment.parent_id,
+      read: !i.props.node.comment.read,
+      auth: null
+    };
+    WebSocketService.Instance.editComment(form);
+  }
+
+
   handleModBanFromCommunityShow(i: CommentNode) {
     i.state.showBanDialog = true;
     i.state.banType = BanType.Community;
@@ -382,7 +402,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     i.setState(i.state);
   }
 
-  addAdmin(i: CommentNode) {
+  handleAddAdmin(i: CommentNode) {
     let form: AddAdminForm = {
       user_id: i.props.node.comment.creator_id,
       added: !i.isAdmin,
index abbb1719077109129af3574e0fa3b5b1318de767..da67bbc7f395e1bb64d3e6132b070f085ad41dde 100644 (file)
@@ -12,6 +12,7 @@ interface CommentNodesProps {
   noIndent?: boolean;
   viewOnly?: boolean;
   locked?: boolean;
+  markable?: boolean;
 }
 
 export class CommentNodes extends Component<CommentNodesProps, CommentNodesState> {
@@ -30,6 +31,7 @@ export class CommentNodes extends Component<CommentNodesProps, CommentNodesState
             locked={this.props.locked} 
             moderators={this.props.moderators}
             admins={this.props.admins}
+            markable={this.props.markable}
             />
         )}
       </div>
diff --git a/ui/src/components/inbox.tsx b/ui/src/components/inbox.tsx
new file mode 100644 (file)
index 0000000..e6ce6d1
--- /dev/null
@@ -0,0 +1,177 @@
+import { Component, linkEvent } from 'inferno';
+import { Link } from 'inferno-router';
+import { Subscription } from "rxjs";
+import { retryWhen, delay, take } from 'rxjs/operators';
+import { UserOperation, Comment, SortType, GetRepliesForm, GetRepliesResponse, CommentResponse } from '../interfaces';
+import { WebSocketService, UserService } from '../services';
+import { msgOp } from '../utils';
+import { CommentNodes } from './comment-nodes';
+
+enum UnreadType {
+  Unread, All
+}
+
+interface InboxState {
+  unreadType: UnreadType;
+  replies: Array<Comment>;
+  sort: SortType;
+  page: number;
+}
+
+export class Inbox extends Component<any, InboxState> {
+
+  private subscription: Subscription;
+  private emptyState: InboxState = {
+    unreadType: UnreadType.Unread,
+    replies: [],
+    sort: SortType.New,
+    page: 1,
+  }
+
+  constructor(props: any, context: any) {
+    super(props, context);
+
+    this.state = this.emptyState;
+
+    this.subscription = WebSocketService.Instance.subject
+    .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
+    .subscribe(
+      (msg) => this.parseMessage(msg),
+        (err) => console.error(err),
+        () => console.log('complete')
+    );
+
+    this.refetch();
+  }
+
+  componentWillUnmount() {
+    this.subscription.unsubscribe();
+  }
+
+  render() {
+    let user = UserService.Instance.user;
+    return (
+      <div class="container">
+        <div class="row">
+          <div class="col-12">
+            <h5>Inbox for <Link to={`/user/${user.id}`}>{user.username}</Link></h5>
+            {this.selects()}
+            {this.replies()}
+            {this.paginator()}
+          </div>
+        </div>
+      </div>
+    )
+  }
+
+  selects() {
+    return (
+      <div className="mb-2">
+        <select value={this.state.unreadType} onChange={linkEvent(this, this.handleUnreadTypeChange)} class="custom-select w-auto">
+          <option disabled>Type</option>
+          <option value={UnreadType.Unread}>Unread</option>
+          <option value={UnreadType.All}>All</option>
+        </select>
+        <select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto ml-2">
+          <option disabled>Sort Type</option>
+          <option value={SortType.New}>New</option>
+          <option value={SortType.TopDay}>Top Day</option>
+          <option value={SortType.TopWeek}>Week</option>
+          <option value={SortType.TopMonth}>Month</option>
+          <option value={SortType.TopYear}>Year</option>
+          <option value={SortType.TopAll}>All</option>
+        </select>
+      </div>
+    )
+
+  }
+
+  replies() {
+    return (
+      <div>
+        {this.state.replies.map(reply => 
+          <CommentNodes nodes={[{comment: reply}]} noIndent viewOnly markable />
+        )}
+      </div>
+    );
+  }
+
+  paginator() {
+    return (
+      <div class="mt-2">
+        {this.state.page > 1 && 
+          <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button>
+        }
+        <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button>
+      </div>
+    );
+  }
+
+  nextPage(i: Inbox) { 
+    i.state.page++;
+    i.setState(i.state);
+    i.refetch();
+  }
+
+  prevPage(i: Inbox) { 
+    i.state.page--;
+    i.setState(i.state);
+    i.refetch();
+  }
+
+  handleUnreadTypeChange(i: Inbox, event: any) {
+    i.state.unreadType = Number(event.target.value);
+    i.state.page = 1;
+    i.setState(i.state);
+    i.refetch();
+  }
+
+  refetch() {
+    let form: GetRepliesForm = {
+      sort: SortType[this.state.sort],
+      unread_only: (this.state.unreadType == UnreadType.Unread),
+      page: this.state.page,
+      limit: 9999,
+    };
+    WebSocketService.Instance.getReplies(form);
+  }
+
+  handleSortChange(i: Inbox, event: any) {
+    i.state.sort = Number(event.target.value);
+    i.state.page = 1;
+    i.setState(i.state);
+    i.refetch();
+  }
+
+  parseMessage(msg: any) {
+    console.log(msg);
+    let op: UserOperation = msgOp(msg);
+    if (msg.error) {
+      alert(msg.error);
+      return;
+    } else if (op == UserOperation.GetReplies) {
+      let res: GetRepliesResponse = msg;
+      this.state.replies = res.replies;
+      this.sendRepliesCount();
+      this.setState(this.state);
+    } else if (op == UserOperation.EditComment) {
+      let res: CommentResponse = msg;
+
+      // If youre in the unread view, just remove it from the list
+      if (this.state.unreadType == UnreadType.Unread && res.comment.read) {
+        this.state.replies = this.state.replies.filter(r => r.id !== res.comment.id);
+      } else {
+        let found = this.state.replies.find(c => c.id == res.comment.id);
+        found.read = res.comment.read;
+      }
+
+      this.sendRepliesCount();
+      this.setState(this.state);
+    }
+  }
+
+  sendRepliesCount() {
+    UserService.Instance.sub.next({user: UserService.Instance.user, unreadCount: this.state.replies.filter(r => !r.read).length});
+  }
+}
+
index 01c70f946c185dbde31c02dc175b1c2e11cfde5d..e3d6f84429c257b5a7a412e4f9225b6d0b88ff7d 100644 (file)
@@ -2,7 +2,7 @@ import { Component } from 'inferno';
 import { Link } from 'inferno-router';
 import { Subscription } from "rxjs";
 import { retryWhen, delay, take } from 'rxjs/operators';
-import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse, ListCommunitiesForm, ListCommunitiesResponse, Community, SortType, GetSiteResponse } from '../interfaces';
+import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse, ListCommunitiesForm, ListCommunitiesResponse, Community, SortType, GetSiteResponse, GetRepliesResponse, GetRepliesForm } from '../interfaces';
 import { WebSocketService, UserService } from '../services';
 import { PostListings } from './post-listings';
 import { msgOp, repoUrl, mdToHtml } from '../utils';
@@ -55,6 +55,15 @@ export class Main extends Component<any, State> {
 
     if (UserService.Instance.user) {
       WebSocketService.Instance.getFollowedCommunities();
+
+      // Get replies for the count
+      let repliesForm: GetRepliesForm = {
+        sort: SortType[SortType.New],
+        unread_only: true,
+        page: 1,
+        limit: 9999,
+      };
+      WebSocketService.Instance.getReplies(repliesForm);
     }
 
     let listCommunitiesForm: ListCommunitiesForm = {
@@ -176,7 +185,14 @@ export class Main extends Component<any, State> {
       this.state.site.site = res.site;
       this.state.site.banned = res.banned;
       this.setState(this.state);
+    } else if (op == UserOperation.GetReplies) {
+      let res: GetRepliesResponse = msg;
+      this.sendRepliesCount(res);
     } 
   }
+
+  sendRepliesCount(res: GetRepliesResponse) {
+    UserService.Instance.sub.next({user: UserService.Instance.user, unreadCount: res.replies.filter(r => !r.read).length});
+  }
 }
 
index be98912eea17aebf639647afdd4aa8666b32d392..fed49e6fd48aa199bfad3c645ee3e99b642a159f 100644 (file)
@@ -7,12 +7,14 @@ interface NavbarState {
   isLoggedIn: boolean;
   expanded: boolean;
   expandUserDropdown: boolean;
+  unreadCount: number;
 }
 
 export class Navbar extends Component<any, NavbarState> {
 
   emptyState: NavbarState = {
-    isLoggedIn: UserService.Instance.user !== undefined,
+    isLoggedIn: (UserService.Instance.user !== undefined),
+    unreadCount: 0,
     expanded: false,
     expandUserDropdown: false
   }
@@ -24,8 +26,9 @@ export class Navbar extends Component<any, NavbarState> {
 
     // Subscribe to user changes
     UserService.Instance.sub.subscribe(user => {
-      let loggedIn: boolean = user !== undefined;
-      this.setState({isLoggedIn: loggedIn});
+      this.state.isLoggedIn = user.user !== undefined;
+      this.state.unreadCount = user.unreadCount;
+      this.setState(this.state);
     });
   }
 
@@ -65,9 +68,13 @@ export class Navbar extends Component<any, NavbarState> {
           <ul class="navbar-nav ml-auto mr-2">
             {this.state.isLoggedIn ? 
             <>
+              {
               <li className="nav-item">
-                <Link class="nav-link" to="/communities">🖂</Link>
+                <Link class="nav-link" to="/inbox">🖂 
+                  {this.state.unreadCount> 0 && <span class="badge badge-light">{this.state.unreadCount}</span>}
+                </Link>
               </li>
+            }
               <li className={`nav-item dropdown ${this.state.expandUserDropdown && 'show'}`}>
                 <a class="pointer nav-link dropdown-toggle" onClick={linkEvent(this, this.expandUserDropdown)} role="button">
                   {UserService.Instance.user.username}
@@ -95,6 +102,7 @@ export class Navbar extends Component<any, NavbarState> {
   handleLogoutClick(i: Navbar) {
     i.state.expandUserDropdown = false;
     UserService.Instance.logout();
+    i.context.router.history.push('/');
   }
 
   handleOverviewClick(i: Navbar) {
index 56b73f6e549864589a56da863930394a70116792..3f243220a061ad3a108c1bd79972f1bd07be154f 100644 (file)
@@ -10,7 +10,6 @@ import { CommentForm } from './comment-form';
 import { CommentNodes } from './comment-nodes';
 import * as autosize from 'autosize';
 
-
 interface PostState {
   post: PostI;
   comments: Array<Comment>;
index 56fc2f4631b3ba9a9a62dd5de1e15d4dba0af9f9..3b74357d56013d4d899ab000664b4b8a23b84c7b 100644 (file)
@@ -82,3 +82,10 @@ blockquote {
   margin: 0.5em 5px;
   padding: 0.1em 5px;
 }
+
+.badge-notify{
+   /* background:red; */
+   position:relative;
+   top: -20px;
+   left: -35px;
+}
index d830bd3ae050f0e1e354e58c03c5ed91d651efe2..677d7678863b57173e11cea72967ca8b0e0b94fa 100644 (file)
@@ -13,6 +13,7 @@ import { Communities } from './components/communities';
 import { User } from './components/user';
 import { Modlog } from './components/modlog';
 import { Setup } from './components/setup';
+import { Inbox } from './components/inbox';
 import { Symbols } from './components/symbols';
 
 import './css/bootstrap.min.css';
@@ -46,6 +47,7 @@ class Index extends Component<any, any> {
             <Route path={`/community/:id`} component={Community} />
             <Route path={`/user/:id/:heading`} component={User} />
             <Route path={`/user/:id`} component={User} />
+            <Route path={`/inbox`} component={Inbox} />
             <Route path={`/modlog/community/:community_id`} component={Modlog} />
             <Route path={`/modlog`} component={Modlog} />
             <Route path={`/setup`} component={Setup} />
index 8927a171de3e0171a5ec054f85d785bfe3cf339d..24bb6157f55a4390b06605016d72f776aaa1d2de 100644 (file)
@@ -1,5 +1,5 @@
 export enum UserOperation {
-  Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser
+  Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser
 }
 
 export enum CommentSortType {
@@ -153,6 +153,19 @@ export interface UserDetailsResponse {
   posts: Array<Post>;
 }
 
+export interface GetRepliesForm {
+  sort: string; // TODO figure this one out
+  page?: number;
+  limit?: number;
+  unread_only: boolean;
+  auth?: string;
+}
+
+export interface GetRepliesResponse {
+  op: string;
+  replies: Array<Comment>;
+}
+
 export interface BanFromCommunityForm {
   community_id: number;
   user_id: number;
@@ -404,6 +417,7 @@ export interface CommentForm {
   creator_id: number;
   removed?: boolean;
   reason?: string;
+  read?: boolean;
   auth: string;
 }
 
index e182134dec4802250071ca51f925f983a4b39e13..d3259adb46718142bbd633f170eaee5e08036030 100644 (file)
@@ -4,9 +4,10 @@ import * as jwt_decode from 'jwt-decode';
 import { Subject } from 'rxjs';
 
 export class UserService {
+
   private static _instance: UserService;
   public user: User;
-  public sub: Subject<User> = new Subject<User>();
+  public sub: Subject<{user: User, unreadCount: number}> = new Subject<{user: User, unreadCount: number}>();
 
   private constructor() {
     let jwt = Cookies.get("jwt");
@@ -28,7 +29,7 @@ export class UserService {
     this.user = undefined;
     Cookies.remove("jwt");
     console.log("Logged out.");
-    this.sub.next(undefined);
+    this.sub.next({user: undefined, unreadCount: 0});
   }
 
   public get auth(): string {
@@ -37,7 +38,7 @@ export class UserService {
 
   private setUser(jwt: string) {
     this.user = jwt_decode(jwt);
-    this.sub.next(this.user);
+    this.sub.next({user: this.user, unreadCount: 0});
     console.log(this.user);
   }
 
index b2c2a9e001bc74fca0b409ac7ede1e3f7386d423..ac59631e00fa21982c0715a409238f2ee5f17719 100644 (file)
@@ -1,5 +1,5 @@
 import { wsUri } from '../env';
-import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, AddAdminForm, BanUserForm, SiteForm, Site, UserView } from '../interfaces';
+import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, AddAdminForm, BanUserForm, SiteForm, Site, UserView, GetRepliesForm } from '../interfaces';
 import { webSocket } from 'rxjs/webSocket';
 import { Subject } from 'rxjs';
 import { retryWhen, delay, take } from 'rxjs/operators';
@@ -145,6 +145,11 @@ export class WebSocketService {
     this.subject.next(this.wsSendWrapper(UserOperation.GetUserDetails, form));
   }
 
+  public getReplies(form: GetRepliesForm) {
+    this.setAuth(form);
+    this.subject.next(this.wsSendWrapper(UserOperation.GetReplies, form));
+  }
+
   public getModlog(form: GetModlogForm) {
     this.subject.next(this.wsSendWrapper(UserOperation.GetModlog, form));
   }