diff options
author | Dessalines <tyhou13@gmx.com> | 2019-03-28 12:32:08 -0700 |
---|---|---|
committer | Dessalines <tyhou13@gmx.com> | 2019-03-28 12:32:08 -0700 |
commit | 49c61fc31818e92fea00f8cd9d1c048104d8ecbf (patch) | |
tree | 3d23898c675722809f585ee81ae53ed9763fe87b | |
parent | 05f0aee3ea88d3982e2efe6230f8c591540e4c92 (diff) |
Adding comment voting
- Extracting out some components.
- Fixing an issue with window refreshing websockets.
-rw-r--r-- | server/migrations/2019-02-27-170003_create_community/up.sql | 2 | ||||
-rw-r--r-- | server/migrations/2019-03-03-163336_create_post/up.sql | 1 | ||||
-rw-r--r-- | server/migrations/2019-03-05-233828_create_comment/up.sql | 4 | ||||
-rw-r--r-- | server/src/actions/comment.rs | 126 | ||||
-rw-r--r-- | server/src/actions/post.rs | 6 | ||||
-rw-r--r-- | server/src/lib.rs | 1 | ||||
-rw-r--r-- | server/src/schema.rs | 2 | ||||
-rw-r--r-- | server/src/websocket_server/server.rs | 149 | ||||
-rw-r--r-- | ui/src/components/post.tsx | 176 | ||||
-rw-r--r-- | ui/src/interfaces.ts | 18 | ||||
-rw-r--r-- | ui/src/services/WebSocketService.ts | 16 |
11 files changed, 400 insertions, 101 deletions
diff --git a/server/migrations/2019-02-27-170003_create_community/up.sql b/server/migrations/2019-02-27-170003_create_community/up.sql index 1ee2e51d..651a9432 100644 --- a/server/migrations/2019-02-27-170003_create_community/up.sql +++ b/server/migrations/2019-02-27-170003_create_community/up.sql @@ -18,3 +18,5 @@ create table community_follower ( fedi_user_id text not null, published timestamp not null default now() ); + +insert into community (name) values ('main'); diff --git a/server/migrations/2019-03-03-163336_create_post/up.sql b/server/migrations/2019-03-03-163336_create_post/up.sql index f22192f3..14294c8f 100644 --- a/server/migrations/2019-03-03-163336_create_post/up.sql +++ b/server/migrations/2019-03-03-163336_create_post/up.sql @@ -16,4 +16,3 @@ create table post_like ( score smallint not null, -- -1, or 1 for dislike, like, no row for no opinion published timestamp not null default now() ); - diff --git a/server/migrations/2019-03-05-233828_create_comment/up.sql b/server/migrations/2019-03-05-233828_create_comment/up.sql index 63fc758d..c80f8d18 100644 --- a/server/migrations/2019-03-05-233828_create_comment/up.sql +++ b/server/migrations/2019-03-05-233828_create_comment/up.sql @@ -11,7 +11,9 @@ create table comment ( create table comment_like ( id serial primary key, comment_id int references comment on update cascade on delete cascade not null, + post_id int references post on update cascade on delete cascade not null, fedi_user_id text not null, score smallint not null, -- -1, or 1 for dislike, like, no row for no opinion - published timestamp not null default now() + published timestamp not null default now(), + unique(comment_id, fedi_user_id) ); diff --git a/server/src/actions/comment.rs b/server/src/actions/comment.rs index 93e808a4..7f2dace6 100644 --- a/server/src/actions/comment.rs +++ b/server/src/actions/comment.rs @@ -36,12 +36,13 @@ pub struct CommentForm { pub updated: Option<chrono::NaiveDateTime> } -#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)] #[belongs_to(Comment)] #[table_name = "comment_like"] pub struct CommentLike { pub id: i32, pub comment_id: i32, + pub post_id: i32, pub fedi_user_id: String, pub score: i16, pub published: chrono::NaiveDateTime, @@ -51,6 +52,7 @@ pub struct CommentLike { #[table_name="comment_like"] pub struct CommentLikeForm { pub comment_id: i32, + pub post_id: i32, pub fedi_user_id: String, pub score: i16 } @@ -70,9 +72,9 @@ impl Crud<CommentForm> for Comment { fn create(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> { use schema::comment::dsl::*; - insert_into(comment) - .values(comment_form) - .get_result::<Self>(conn) + insert_into(comment) + .values(comment_form) + .get_result::<Self>(conn) } fn update(conn: &PgConnection, comment_id: i32, comment_form: &CommentForm) -> Result<Self, Error> { @@ -84,6 +86,13 @@ impl Crud<CommentForm> for Comment { } impl Likeable <CommentLikeForm> for CommentLike { + fn read(conn: &PgConnection, comment_id_from: i32) -> Result<Vec<Self>, Error> { + use schema::comment_like::dsl::*; + comment_like + .filter(comment_id.eq(comment_id_from)) + .load::<Self>(conn) + } + fn like(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<Self, Error> { use schema::comment_like::dsl::*; insert_into(comment_like) @@ -93,21 +102,116 @@ impl Likeable <CommentLikeForm> for CommentLike { fn remove(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<usize, Error> { use schema::comment_like::dsl::*; diesel::delete(comment_like - .filter(comment_id.eq(comment_like_form.comment_id)) - .filter(fedi_user_id.eq(&comment_like_form.fedi_user_id))) + .filter(comment_id.eq(comment_like_form.comment_id)) + .filter(fedi_user_id.eq(&comment_like_form.fedi_user_id))) .execute(conn) } } +impl CommentLike { + pub fn from_post(conn: &PgConnection, post_id_from: i32) -> Result<Vec<Self>, Error> { + use schema::comment_like::dsl::*; + comment_like + .filter(post_id.eq(post_id_from)) + .load::<Self>(conn) + } +} + + + impl Comment { - pub fn from_post(conn: &PgConnection, post: &Post) -> Result<Vec<Self>, Error> { - use schema::community::dsl::*; - Comment::belonging_to(post) - .order_by(comment::published.desc()) + fn from_post(conn: &PgConnection, post_id_from: i32) -> Result<Vec<Self>, Error> { + use schema::comment::dsl::*; + comment + .filter(post_id.eq(post_id_from)) + .order_by(published.desc()) .load::<Self>(conn) } } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CommentView { + pub id: i32, + pub content: String, + pub attributed_to: String, + pub post_id: i32, + pub parent_id: Option<i32>, + pub published: chrono::NaiveDateTime, + pub updated: Option<chrono::NaiveDateTime>, + pub score: i32, + pub upvotes: i32, + pub downvotes: i32, + pub my_vote: Option<i16> +} + +impl CommentView { + fn from_comment(comment: &Comment, likes: &Vec<CommentLike>, fedi_user_id: &Option<String>) -> Self { + let mut upvotes: i32 = 0; + let mut downvotes: i32 = 0; + let mut my_vote: Option<i16> = Some(0); + + for like in likes.iter() { + if like.score == 1 { + upvotes += 1 + } else if like.score == -1 { + downvotes += 1; + } + + if let Some(user) = fedi_user_id { + if like.fedi_user_id == *user { + my_vote = Some(like.score); + } + } + + } + + let score: i32 = upvotes - downvotes; + + CommentView { + id: comment.id, + content: comment.content.to_owned(), + parent_id: comment.parent_id, + post_id: comment.post_id, + attributed_to: comment.attributed_to.to_owned(), + published: comment.published, + updated: None, + upvotes: upvotes, + score: score, + downvotes: downvotes, + my_vote: my_vote + } + } + + pub fn from_new_comment(comment: &Comment) -> Self { + Self::from_comment(comment, &Vec::new(), &None) + } + + pub fn read(conn: &PgConnection, comment_id: i32, fedi_user_id: &Option<String>) -> Self { + let comment = Comment::read(&conn, comment_id).unwrap(); + let likes = CommentLike::read(&conn, comment_id).unwrap(); + Self::from_comment(&comment, &likes, fedi_user_id) + } + + pub fn from_post(conn: &PgConnection, post_id: i32, fedi_user_id: &Option<String>) -> Vec<Self> { + let comments = Comment::from_post(&conn, post_id).unwrap(); + let post_comment_likes = CommentLike::from_post(&conn, post_id).unwrap(); + + let mut views = Vec::new(); + for comment in comments.iter() { + let comment_likes: Vec<CommentLike> = post_comment_likes + .iter() + .filter(|like| comment.id == like.comment_id) + .cloned() + .collect(); + let comment_view = CommentView::from_comment(&comment, &comment_likes, fedi_user_id); + views.push(comment_view); + }; + + views + } +} + + #[cfg(test)] mod tests { use establish_connection; @@ -169,6 +273,7 @@ mod tests { let comment_like_form = CommentLikeForm { comment_id: inserted_comment.id, + post_id: inserted_post.id, fedi_user_id: "test".into(), score: 1 }; @@ -178,6 +283,7 @@ mod tests { let expected_comment_like = CommentLike { id: inserted_comment_like.id, comment_id: inserted_comment.id, + post_id: inserted_post.id, fedi_user_id: "test".into(), published: inserted_comment_like.published, score: 1 diff --git a/server/src/actions/post.rs b/server/src/actions/post.rs index 71846dff..fff87dfd 100644 --- a/server/src/actions/post.rs +++ b/server/src/actions/post.rs @@ -77,6 +77,12 @@ impl Crud<PostForm> for Post { } impl Likeable <PostLikeForm> for PostLike { + fn read(conn: &PgConnection, post_id_from: i32) -> Result<Vec<Self>, Error> { + use schema::post_like::dsl::*; + post_like + .filter(post_id.eq(post_id_from)) + .load::<Self>(conn) + } fn like(conn: &PgConnection, post_like_form: &PostLikeForm) -> Result<Self, Error> { use schema::post_like::dsl::*; insert_into(post_like) diff --git a/server/src/lib.rs b/server/src/lib.rs index fcc9c2c8..0d81d507 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -43,6 +43,7 @@ pub trait Joinable<T> { } pub trait Likeable<T> { + fn read(conn: &PgConnection, id: i32) -> Result<Vec<Self>, Error> where Self: Sized; fn like(conn: &PgConnection, form: &T) -> Result<Self, Error> where Self: Sized; fn remove(conn: &PgConnection, form: &T) -> Result<usize, Error> where Self: Sized; } diff --git a/server/src/schema.rs b/server/src/schema.rs index 28c4e8ca..93add9ba 100644 --- a/server/src/schema.rs +++ b/server/src/schema.rs @@ -14,6 +14,7 @@ table! { comment_like (id) { id -> Int4, comment_id -> Int4, + post_id -> Int4, fedi_user_id -> Text, score -> Int2, published -> Timestamp, @@ -85,6 +86,7 @@ table! { joinable!(comment -> post (post_id)); joinable!(comment_like -> comment (comment_id)); +joinable!(comment_like -> post (post_id)); joinable!(community_follower -> community (community_id)); joinable!(community_user -> community (community_id)); joinable!(post -> community (community_id)); diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index d257f4c0..78a71ec8 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -9,8 +9,9 @@ use serde::{Deserialize, Serialize}; use serde_json::{Result, Value}; use bcrypt::{verify}; use std::str::FromStr; +use std::{thread, time}; -use {Crud, Joinable, establish_connection}; +use {Crud, Joinable, Likeable, establish_connection}; use actions::community::*; use actions::user::*; use actions::post::*; @@ -19,7 +20,7 @@ use actions::comment::*; #[derive(EnumString,ToString,Debug)] pub enum UserOperation { - Login, Register, Logout, CreateCommunity, ListCommunities, CreatePost, GetPost, GetCommunity, CreateComment, Join, Edit, Reply, Vote, Delete, NextPage, Sticky + Login, Register, Logout, CreateCommunity, ListCommunities, CreatePost, GetPost, GetCommunity, CreateComment, CreateCommentLike, Join, Edit, Reply, Vote, Delete, NextPage, Sticky } @@ -151,14 +152,15 @@ pub struct CreatePostResponse { #[derive(Serialize, Deserialize)] pub struct GetPost { - id: i32 + id: i32, + auth: Option<String> } #[derive(Serialize, Deserialize)] pub struct GetPostResponse { op: String, post: Post, - comments: Vec<Comment> + comments: Vec<CommentView> } #[derive(Serialize, Deserialize)] @@ -183,7 +185,22 @@ pub struct CreateComment { #[derive(Serialize, Deserialize)] pub struct CreateCommentResponse { op: String, - comment: Comment + comment: CommentView +} + + +#[derive(Serialize, Deserialize)] +pub struct CreateCommentLike { + comment_id: i32, + post_id: i32, + score: i16, + auth: String +} + +#[derive(Serialize, Deserialize)] +pub struct CreateCommentLikeResponse { + op: String, + comment: CommentView } /// `ChatServer` manages chat rooms and responsible for coordinating chat @@ -343,6 +360,10 @@ impl Handler<StandardMessage> for ChatServer { let create_comment: CreateComment = serde_json::from_str(&data.to_string()).unwrap(); create_comment.perform(self, msg.id) }, + UserOperation::CreateCommentLike => { + let create_comment_like: CreateCommentLike = serde_json::from_str(&data.to_string()).unwrap(); + create_comment_like.perform(self, msg.id) + }, _ => { let e = ErrorMessage { op: "Unknown".to_string(), @@ -576,6 +597,22 @@ impl Perform for GetPost { let conn = establish_connection(); + println!("{:?}", self.auth); + + let fedi_user_id: Option<String> = match &self.auth { + Some(auth) => { + match Claims::decode(&auth) { + Ok(claims) => { + let user_id = claims.claims.id; + let iss = claims.claims.iss; + Some(format!("{}/{}", iss, user_id)) + } + Err(e) => None + } + } + None => None + }; + let post = match Post::read(&conn, self.id) { Ok(post) => post, Err(e) => { @@ -583,37 +620,21 @@ impl Perform for GetPost { } }; - - // let mut rooms = Vec::new(); - // remove session from all rooms for (n, sessions) in &mut chat.rooms { - // if sessions.remove(&addr) { - // // rooms.push(*n); - // } sessions.remove(&addr); } - // // send message to other users - // for room in rooms { - // self.send_room_message(&room, "Someone disconnected", 0); - // } if chat.rooms.get_mut(&self.id).is_none() { chat.rooms.insert(self.id, HashSet::new()); } - // TODO send a Joined response - - - - // chat.send_room_message(addr,) - // self.send_room_message(&name, "Someone connected", id); chat.rooms.get_mut(&self.id).unwrap().insert(addr); - let comments = Comment::from_post(&conn, &post).unwrap(); + let comments = CommentView::from_post(&conn, post.id, &fedi_user_id); - println!("{:?}", chat.rooms.keys()); - println!("{:?}", chat.rooms.get(&5i32).unwrap()); + // println!("{:?}", chat.rooms.keys()); + // println!("{:?}", chat.rooms.get(&5i32).unwrap()); // Return the jwt serde_json::to_string( @@ -688,24 +709,98 @@ impl Perform for CreateComment { } }; + // TODO You like your own comment by default + + // Simulate a comment view to get back blank score, no need to fetch anything + let comment_view = CommentView::from_new_comment(&inserted_comment); + let comment_out = serde_json::to_string( &CreateCommentResponse { op: self.op_type().to_string(), - comment: inserted_comment + comment: comment_view } ) .unwrap(); chat.send_room_message(self.post_id, &comment_out, addr); - println!("{:?}", chat.rooms.keys()); - println!("{:?}", chat.rooms.get(&5i32).unwrap()); + // println!("{:?}", chat.rooms.keys()); + // println!("{:?}", chat.rooms.get(&5i32).unwrap()); comment_out } } +impl Perform for CreateCommentLike { + fn op_type(&self) -> UserOperation { + UserOperation::CreateCommentLike + } + + 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 iss = claims.iss; + let fedi_user_id = format!("{}/{}", iss, user_id); + + let like_form = CommentLikeForm { + comment_id: self.comment_id, + post_id: self.post_id, + fedi_user_id: fedi_user_id.to_owned(), + score: self.score + }; + + // Remove any likes first + CommentLike::remove(&conn, &like_form).unwrap(); + + // Only add the like if the score isnt 0 + if &like_form.score != &0 { + let inserted_like = match CommentLike::like(&conn, &like_form) { + Ok(like) => like, + Err(e) => { + return self.error("Couldn't like comment."); + } + }; + } + + // Have to refetch the comment to get the current state + // thread::sleep(time::Duration::from_secs(1)); + let liked_comment = CommentView::read(&conn, self.comment_id, &Some(fedi_user_id)); + + let mut liked_comment_sent = liked_comment.clone(); + liked_comment_sent.my_vote = None; + + let like_out = serde_json::to_string( + &CreateCommentLikeResponse { + op: self.op_type().to_string(), + comment: liked_comment + } + ) + .unwrap(); + + let like_sent_out = serde_json::to_string( + &CreateCommentLikeResponse { + op: self.op_type().to_string(), + comment: liked_comment_sent + } + ) + .unwrap(); + + chat.send_room_message(self.post_id, &like_sent_out, addr); + + like_out + } +} + // impl Handler<Login> for ChatServer { diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx index 867e1a4a..2a780cf7 100644 --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@ -1,7 +1,7 @@ import { Component, linkEvent } from 'inferno'; import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; -import { UserOperation, Community, Post as PostI, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse } from '../interfaces'; +import { UserOperation, Community, Post as PostI, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CreateCommentLikeResponse } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { msgOp } from '../utils'; import { MomentTime } from './moment-time'; @@ -9,7 +9,6 @@ import { MomentTime } from './moment-time'; interface CommentNodeI { comment: Comment; children?: Array<CommentNodeI>; - showReply?: boolean; }; interface State { @@ -78,7 +77,7 @@ export class Post extends Component<any, State> { ? <h5> <a href={this.state.post.url}>{this.state.post.name}</a> <small><a className="ml-2 text-muted font-italic" href={this.state.post.url}>{(new URL(this.state.post.url)).hostname}</a></small> - </h5> + </h5> : <h5>{this.state.post.name}</h5>; return ( <div> @@ -141,7 +140,6 @@ export class Post extends Component<any, State> { ); } - parseMessage(msg: any) { console.log(msg); let op: UserOperation = msgOp(msg); @@ -157,6 +155,16 @@ export class Post extends Component<any, State> { let res: CommentResponse = msg; this.state.comments.unshift(res.comment); this.setState(this.state); + } else if (op == UserOperation.CreateCommentLike) { + let res: CreateCommentLikeResponse = msg; + let found: Comment = this.state.comments.find(c => c.id === res.comment.id); + found.score = res.comment.score; + found.upvotes = res.comment.upvotes; + found.downvotes = res.comment.downvotes; + if (res.comment.my_vote !== null) + found.my_vote = res.comment.my_vote; + console.log(res.comment.my_vote); + this.setState(this.state); } } @@ -174,75 +182,128 @@ export class CommentNodes extends Component<CommentNodesProps, CommentNodesState constructor(props, context) { super(props, context); - this.handleReplyClick = this.handleReplyClick.bind(this); - this.handleReplyCancel = this.handleReplyCancel.bind(this); } render() { return ( <div className="comments"> {this.props.nodes.map(node => - <div className={`comment ${node.comment.parent_id && !this.props.noIndent ? 'ml-4' : ''}`}> - <div className="float-left small text-center"> - <div className="pointer upvote">▲</div> - <div>20</div> - <div className="pointer downvote">▼</div> - </div> - <div className="details ml-4"> - <ul class="list-inline mb-0 text-muted small"> - <li className="list-inline-item"> - <a href={node.comment.attributed_to}>{node.comment.attributed_to}</a> - </li> - <li className="list-inline-item"> - <span>( - <span className="text-info">+1300</span> - <span> | </span> - <span className="text-danger">-29</span> - <span>) </span> - </span> - </li> - <li className="list-inline-item"> - <span><MomentTime data={node.comment} /></span> - </li> - </ul> - <p className="mb-0">{node.comment.content}</p> - <ul class="list-inline mb-1 text-muted small font-weight-bold"> - <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(node, this.handleReplyClick)}>reply</span> - </li> - <li className="list-inline-item"> - <a className="text-muted" href="test">link</a> - </li> - </ul> - </div> - {node.showReply && <CommentForm node={node} onReplyCancel={this.handleReplyCancel} />} - {node.children && <CommentNodes nodes={node.children}/>} - </div> + <CommentNode node={node} noIndent={this.props.noIndent} /> )} </div> ) } +} - handleReplyClick(i: CommentNodeI, event) { - i.showReply = true; - this.setState(this.state); + +interface CommentNodeState { + showReply: boolean; +} + +interface CommentNodeProps { + node: CommentNodeI; + noIndent?: boolean; +} + +export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { + + private emptyState: CommentNodeState = { + showReply: false + } + + constructor(props, context) { + super(props, context); + + this.state = this.emptyState; + this.handleReplyCancel = this.handleReplyCancel.bind(this); + this.handleCommentLike = this.handleCommentLike.bind(this); + this.handleCommentDisLike = this.handleCommentDisLike.bind(this); + } + + render() { + let node = this.props.node; + return ( + <div className={`comment ${node.comment.parent_id && !this.props.noIndent ? 'ml-4' : ''}`}> + <div className="float-left small text-center"> + <div className={`pointer upvote ${node.comment.my_vote == 1 ? 'text-info' : 'text-muted'}`} onClick={linkEvent(node, this.handleCommentLike)}>▲</div> + <div>{node.comment.score}</div> + <div className={`pointer downvote ${node.comment.my_vote == -1 && 'text-danger'}`} onClick={linkEvent(node, this.handleCommentDisLike)}>▼</div> + </div> + <div className="details ml-4"> + <ul class="list-inline mb-0 text-muted small"> + <li className="list-inline-item"> + <a href={node.comment.attributed_to}>{node.comment.attributed_to}</a> + </li> + <li className="list-inline-item"> + <span>( + <span className="text-info">+{node.comment.upvotes}</span> + <span> | </span> + <span className="text-danger">-{node.comment.downvotes}</span> + <span>) </span> + </span> + </li> + <li className="list-inline-item"> + <span><MomentTime data={node.comment} /></span> + </li> + </ul> + <p className="mb-0">{node.comment.content}</p> + <ul class="list-inline mb-1 text-muted small font-weight-bold"> + <li className="list-inline-item"> + <span class="pointer" onClick={linkEvent(this, this.handleReplyClick)}>reply</span> + </li> + <li className="list-inline-item"> + <a className="text-muted" href="test">link</a> + </li> + </ul> + </div> + {this.state.showReply && <CommentForm node={node} onReplyCancel={this.handleReplyCancel} />} + {this.props.node.children && <CommentNodes nodes={this.props.node.children}/>} + </div> + ) + } + + private getScore(): number { + return (this.props.node.comment.upvotes - this.props.node.comment.downvotes) || 0; + } + + handleReplyClick(i: CommentNode, event) { + i.state.showReply = true; + i.setState(i.state); } - handleReplyCancel(i: CommentNodeI): any { - i.showReply = false; + handleReplyCancel(): any { + this.state.showReply = false; this.setState(this.state); } + + handleCommentLike(i: CommentNodeI, event) { + + let form: CommentLikeForm = { + comment_id: i.comment.id, + post_id: i.comment.post_id, + score: (i.comment.my_vote == 1) ? 0 : 1 + }; + WebSocketService.Instance.likeComment(form); + } + + handleCommentDisLike(i: CommentNodeI, event) { + let form: CommentLikeForm = { + comment_id: i.comment.id, + post_id: i.comment.post_id, + score: (i.comment.my_vote == -1) ? 0 : -1 + }; + WebSocketService.Instance.likeComment(form); + } } interface CommentFormProps { postId?: number; node?: CommentNodeI; - onReplyCancel?(node: CommentNodeI); + onReplyCancel?(); } interface CommentFormState { commentForm: CommentFormI; - topReply: boolean; } export class CommentForm extends Component<CommentFormProps, CommentFormState> { @@ -253,8 +314,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { content: null, post_id: null, parent_id: null - }, - topReply: true + } } constructor(props, context) { @@ -262,16 +322,11 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { this.state = this.emptyState; if (this.props.node) { - this.state.topReply = false; this.state.commentForm.post_id = this.props.node.comment.post_id; this.state.commentForm.parent_id = this.props.node.comment.id; } else { this.state.commentForm.post_id = this.props.postId; } - - console.log(this.state); - - this.handleReplyCancel = this.handleReplyCancel.bind(this); } render() { @@ -286,7 +341,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { <div class="row"> <div class="col-sm-12"> <button type="submit" class="btn btn-secondary mr-2">Post</button> - {!this.state.topReply && <button type="button" class="btn btn-secondary" onClick={this.handleReplyCancel}>Cancel</button>} + {this.props.node && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}>Cancel</button>} </div> </div> </form> @@ -299,6 +354,9 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { i.state.commentForm.content = undefined; i.setState(i.state); event.target.reset(); + if (i.props.node) { + i.props.onReplyCancel(); + } } handleCommentContentChange(i: CommentForm, event) { @@ -306,7 +364,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { i.state.commentForm.content = event.target.value; } - handleReplyCancel(event) { - this.props.onReplyCancel(this.props.node); + handleReplyCancel(i: CommentForm, event) { + i.props.onReplyCancel(); } } diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 14c28438..d499eb0a 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -1,5 +1,5 @@ export enum UserOperation { - Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment + Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, CreateCommentLike } export interface User { @@ -63,6 +63,10 @@ export interface Comment { parent_id?: number; published: string; updated?: string; + score: number; + upvotes: number; + downvotes: number; + my_vote?: number; } export interface CommentForm { @@ -77,6 +81,18 @@ export interface CommentResponse { comment: Comment; } +export interface CommentLikeForm { + comment_id: number; + post_id: number; + score: number; + auth?: string; +} + +export interface CreateCommentLikeResponse { + op: string; + comment: Comment; +} + export interface LoginForm { username_or_email: string; password: string; diff --git a/ui/src/services/WebSocketService.ts b/ui/src/services/WebSocketService.ts index beefac85..ed08fa1e 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, CommentForm } from '../interfaces'; +import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm } from '../interfaces'; import { webSocket } from 'rxjs/webSocket'; import { Subject } from 'rxjs'; import { retryWhen, delay, take } from 'rxjs/operators'; @@ -47,7 +47,8 @@ export class WebSocketService { } public getPost(postId: number) { - this.subject.next(this.wsSendWrapper(UserOperation.GetPost, {id: postId})); + let data = {id: postId, auth: UserService.Instance.auth }; + this.subject.next(this.wsSendWrapper(UserOperation.GetPost, data)); } public getCommunity(communityId: number) { @@ -59,6 +60,11 @@ export class WebSocketService { this.subject.next(this.wsSendWrapper(UserOperation.CreateComment, commentForm)); } + public likeComment(form: CommentLikeForm) { + this.setAuth(form); + this.subject.next(this.wsSendWrapper(UserOperation.CreateCommentLike, form)); + } + private wsSendWrapper(op: UserOperation, data: any) { let send = { op: UserOperation[op], data: data }; console.log(send); @@ -72,4 +78,10 @@ export class WebSocketService { throw "Not logged in"; } } + } + +window.onbeforeunload = (e => { + WebSocketService.Instance.subject.unsubscribe(); +}); + |