diff options
-rw-r--r-- | server/migrations/2019-04-03-155309_create_comment_view/down.sql | 1 | ||||
-rw-r--r-- | server/migrations/2019-04-03-155309_create_comment_view/up.sql | 25 | ||||
-rw-r--r-- | server/src/actions/comment.rs | 3 | ||||
-rw-r--r-- | server/src/actions/comment_view.rs | 101 | ||||
-rw-r--r-- | server/src/websocket_server/server.rs | 57 | ||||
-rw-r--r-- | ui/src/components/comment-node.tsx | 24 | ||||
-rw-r--r-- | ui/src/components/comment-nodes.tsx | 2 | ||||
-rw-r--r-- | ui/src/components/inbox.tsx | 177 | ||||
-rw-r--r-- | ui/src/components/main.tsx | 18 | ||||
-rw-r--r-- | ui/src/components/navbar.tsx | 16 | ||||
-rw-r--r-- | ui/src/components/post.tsx | 1 | ||||
-rw-r--r-- | ui/src/css/main.css | 7 | ||||
-rw-r--r-- | ui/src/index.tsx | 2 | ||||
-rw-r--r-- | ui/src/interfaces.ts | 16 | ||||
-rw-r--r-- | ui/src/services/UserService.ts | 7 | ||||
-rw-r--r-- | ui/src/services/WebSocketService.ts | 7 |
16 files changed, 450 insertions, 14 deletions
diff --git a/server/migrations/2019-04-03-155309_create_comment_view/down.sql b/server/migrations/2019-04-03-155309_create_comment_view/down.sql index 2da934a4..c19d5ff7 100644 --- a/server/migrations/2019-04-03-155309_create_comment_view/down.sql +++ b/server/migrations/2019-04-03-155309_create_comment_view/down.sql @@ -1 +1,2 @@ +drop view reply_view; drop view comment_view; diff --git a/server/migrations/2019-04-03-155309_create_comment_view/up.sql b/server/migrations/2019-04-03-155309_create_comment_view/up.sql index a78e3ac3..24ce98fc 100644 --- a/server/migrations/2019-04-03-155309_create_comment_view/up.sql +++ b/server/migrations/2019-04-03-155309_create_comment_view/up.sql @@ -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 +; + diff --git a/server/src/actions/comment.rs b/server/src/actions/comment.rs index 36da90c6..9bb6d018 100644 --- a/server/src/actions/comment.rs +++ b/server/src/actions/comment.rs @@ -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 }; diff --git a/server/src/actions/comment_view.rs b/server/src/actions/comment_view.rs index 4e3d99b7..e8b96e3a 100644 --- a/server/src/actions/comment_view.rs +++ b/server/src/actions/comment_view.rs @@ -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; diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index 63d767c2..dbcf5c5d 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -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 diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index 90cf5a54..cf7b1bce 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -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, diff --git a/ui/src/components/comment-nodes.tsx b/ui/src/components/comment-nodes.tsx index abbb1719..da67bbc7 100644 --- a/ui/src/components/comment-nodes.tsx +++ b/ui/src/components/comment-nodes.tsx @@ -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 index 00000000..e6ce6d13 --- /dev/null +++ b/ui/src/components/inbox.tsx @@ -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}); + } +} + diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx index 01c70f94..e3d6f844 100644 --- a/ui/src/components/main.tsx +++ b/ui/src/components/main.tsx @@ -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}); + } } diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index be98912e..fed49e6f 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -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) { diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx index 56b73f6e..3f243220 100644 --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@ -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>; diff --git a/ui/src/css/main.css b/ui/src/css/main.css index 56fc2f46..3b74357d 100644 --- a/ui/src/css/main.css +++ b/ui/src/css/main.css @@ -82,3 +82,10 @@ blockquote { margin: 0.5em 5px; padding: 0.1em 5px; } + +.badge-notify{ + /* background:red; */ + position:relative; + top: -20px; + left: -35px; +} diff --git a/ui/src/index.tsx b/ui/src/index.tsx index d830bd3a..677d7678 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -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} /> diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 8927a171..24bb6157 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -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; } diff --git a/ui/src/services/UserService.ts b/ui/src/services/UserService.ts index e182134d..d3259adb 100644 --- a/ui/src/services/UserService.ts +++ b/ui/src/services/UserService.ts @@ -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); } diff --git a/ui/src/services/WebSocketService.ts b/ui/src/services/WebSocketService.ts index b2c2a9e0..ac59631e 100644 --- a/ui/src/services/WebSocketService.ts +++ b/ui/src/services/WebSocketService.ts @@ -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)); } |