use rand::{rngs::ThreadRng, Rng};
use std::collections::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
-use serde_json::{Result, Value};
+use serde_json::{Value};
use bcrypt::{verify};
use std::str::FromStr;
-use std::{thread, time};
-use {Crud, Joinable, Likeable, establish_connection};
+use {Crud, Joinable, Likeable, establish_connection, naive_now};
use actions::community::*;
use actions::user::*;
use actions::post::*;
#[derive(EnumString,ToString,Debug)]
pub enum UserOperation {
- Login, Register, Logout, CreateCommunity, ListCommunities, CreatePost, GetPost, GetCommunity, CreateComment, CreateCommentLike, Join, Edit, Reply, Vote, Delete, NextPage, Sticky
+ Login, Register, Logout, CreateCommunity, ListCommunities, CreatePost, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, Join, Edit, Reply, Vote, Delete, NextPage, Sticky
}
pub struct CreateComment {
content: String,
parent_id: Option<i32>,
+ edit_id: Option<i32>,
post_id: i32,
auth: String
}
}
+#[derive(Serialize, Deserialize)]
+pub struct EditComment {
+ content: String,
+ parent_id: Option<i32>,
+ edit_id: i32,
+ post_id: i32,
+ auth: String
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct EditCommentResponse {
+ op: String,
+ comment: CommentView
+}
+
#[derive(Serialize, Deserialize)]
pub struct CreateCommentLike {
comment_id: i32,
let create_comment: CreateComment = serde_json::from_str(&data.to_string()).unwrap();
create_comment.perform(self, msg.id)
},
+ UserOperation::EditComment => {
+ let edit_comment: EditComment = serde_json::from_str(&data.to_string()).unwrap();
+ edit_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 user_id = claims.id;
+ let username = claims.username;
let iss = claims.iss;
+ let fedi_user_id = format!("{}/{}", iss, username);
let community_form = CommunityForm {
name: self.name.to_owned(),
let community_user_form = CommunityUserForm {
community_id: inserted_community.id,
- fedi_user_id: format!("{}/{}", iss, user_id)
+ fedi_user_id: fedi_user_id
};
let inserted_community_user = match CommunityUser::join(&conn, &community_user_form) {
};
let user_id = claims.id;
+ let username = claims.username;
let iss = claims.iss;
-
+ let fedi_user_id = format!("{}/{}", iss, username);
let post_form = PostForm {
name: self.name.to_owned(),
url: self.url.to_owned(),
body: self.body.to_owned(),
community_id: self.community_id,
- attributed_to: format!("{}/{}", iss, user_id),
+ attributed_to: fedi_user_id,
updated: None
};
Some(auth) => {
match Claims::decode(&auth) {
Ok(claims) => {
- let user_id = claims.claims.id;
+ let username = claims.claims.username;
let iss = claims.claims.iss;
- Some(format!("{}/{}", iss, user_id))
+ let fedi_user_id = format!("{}/{}", iss, username);
+ Some(fedi_user_id)
}
Err(e) => None
}
};
let user_id = claims.id;
+ let username = claims.username;
let iss = claims.iss;
- let fedi_user_id = format!("{}/{}", iss, user_id);
+ let fedi_user_id = format!("{}/{}", iss, username);
let comment_form = CommentForm {
content: self.content.to_owned(),
let comment_view = CommentView::from_comment(&inserted_comment, &likes, &Some(fedi_user_id));
-
let mut comment_sent = comment_view.clone();
comment_sent.my_vote = None;
)
.unwrap();
-
let comment_sent_out = serde_json::to_string(
&CreateCommentLikeResponse {
op: self.op_type().to_string(),
}
}
+impl Perform for EditComment {
+ fn op_type(&self) -> UserOperation {
+ UserOperation::EditComment
+ }
+
+ 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 username = claims.username;
+ let iss = claims.iss;
+ let fedi_user_id = format!("{}/{}", iss, username);
+
+ let comment_form = CommentForm {
+ content: self.content.to_owned(),
+ parent_id: self.parent_id,
+ post_id: self.post_id,
+ attributed_to: fedi_user_id.to_owned(),
+ updated: Some(naive_now())
+ };
+
+ let updated_comment = match Comment::update(&conn, self.edit_id, &comment_form) {
+ Ok(comment) => comment,
+ Err(e) => {
+ return self.error("Couldn't update Comment");
+ }
+ };
+
+ let likes = match CommentLike::read(&conn, self.edit_id) {
+ Ok(likes) => likes,
+ Err(e) => {
+ return self.error("Couldn't get likes");
+ }
+ };
+
+ let comment_view = CommentView::from_comment(&updated_comment, &likes, &Some(fedi_user_id));
+
+ let mut comment_sent = comment_view.clone();
+ comment_sent.my_vote = None;
+
+ let comment_out = serde_json::to_string(
+ &CreateCommentResponse {
+ op: self.op_type().to_string(),
+ comment: comment_view
+ }
+ )
+ .unwrap();
+
+ let comment_sent_out = serde_json::to_string(
+ &CreateCommentLikeResponse {
+ op: self.op_type().to_string(),
+ comment: comment_sent
+ }
+ )
+ .unwrap();
+
+ chat.send_room_message(self.post_id, &comment_sent_out, addr);
+
+ comment_out
+ }
+}
impl Perform for CreateCommentLike {
fn op_type(&self) -> UserOperation {
};
let user_id = claims.id;
+ let username = claims.username;
let iss = claims.iss;
- let fedi_user_id = format!("{}/{}", iss, user_id);
+ let fedi_user_id = format!("{}/{}", iss, username);
let like_form = CommentLikeForm {
comment_id: self.comment_id,
newComments() {
return (
<div class="sticky-top">
- <h4>New Comments</h4>
+ <h5>New Comments</h5>
{this.state.comments.map(comment =>
<CommentNodes nodes={[{comment: comment}]} noIndent />
)}
sidebar() {
return (
<div class="sticky-top">
- <h4>Sidebar</h4>
+ <h5>Sidebar</h5>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
);
let res: CommentResponse = msg;
this.state.comments.unshift(res.comment);
this.setState(this.state);
- } else if (op == UserOperation.CreateCommentLike) {
+ } else if (op == UserOperation.EditComment) {
+ let res: CommentResponse = msg;
+ let found = this.state.comments.find(c => c.id == res.comment.id);
+ found.content = res.comment.content;
+ found.updated = res.comment.updated;
+ 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.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);
}
interface CommentNodeState {
showReply: boolean;
+ showEdit: boolean;
}
interface CommentNodeProps {
export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
private emptyState: CommentNodeState = {
- showReply: false
+ showReply: false,
+ showEdit: false
}
constructor(props, context) {
<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>
+ {this.state.showEdit && <CommentForm node={node} edit onReplyCancel={this.handleReplyCancel} />}
+ {!this.state.showEdit &&
+ <div>
+ <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>
+ {this.myComment &&
+ <li className="list-inline-item">
+ <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
+ </li>
+ }
+ <li className="list-inline-item">
+ <a className="text-muted" href="test">link</a>
+ </li>
+ </ul>
+ </div>
+ }
</div>
{this.state.showReply && <CommentForm node={node} onReplyCancel={this.handleReplyCancel} />}
{this.props.node.children && <CommentNodes nodes={this.props.node.children}/>}
)
}
- private getScore(): number {
- return (this.props.node.comment.upvotes - this.props.node.comment.downvotes) || 0;
+ private get myComment(): boolean {
+ return this.props.node.comment.attributed_to == UserService.Instance.fediUserId;
}
handleReplyClick(i: CommentNode, event) {
i.setState(i.state);
}
+ handleEditClick(i: CommentNode, event) {
+ i.state.showEdit = true;
+ i.setState(i.state);
+ }
+
handleReplyCancel(): any {
this.state.showReply = false;
+ this.state.showEdit = false;
this.setState(this.state);
}
+
handleCommentLike(i: CommentNodeI, event) {
let form: CommentLikeForm = {
postId?: number;
node?: CommentNodeI;
onReplyCancel?();
+ edit?: boolean;
}
interface CommentFormState {
commentForm: CommentFormI;
+ buttonTitle: string;
}
export class CommentForm extends Component<CommentFormProps, CommentFormState> {
commentForm: {
auth: null,
content: null,
- post_id: null,
- parent_id: null
- }
+ post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId
+ },
+ buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply"
}
constructor(props, context) {
super(props, context);
this.state = this.emptyState;
+
if (this.props.node) {
- 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;
- }
+ if (this.props.edit) {
+ this.state.commentForm.edit_id = this.props.node.comment.id;
+ this.state.commentForm.parent_id = this.props.node.comment.parent_id;
+ this.state.commentForm.content = this.props.node.comment.content;
+ } else {
+ // A reply gets a new parent id
+ this.state.commentForm.parent_id = this.props.node.comment.id;
+ }
+ }
}
+
render() {
return (
<div>
- <form onSubmit={linkEvent(this, this.handleCreateCommentSubmit)}>
+ <form onSubmit={linkEvent(this, this.handleCommentSubmit)}>
<div class="form-group row">
<div class="col-sm-12">
<textarea class="form-control" value={this.state.commentForm.content} onInput={linkEvent(this, this.handleCommentContentChange)} placeholder="Comment here" required />
</div>
<div class="row">
<div class="col-sm-12">
- <button type="submit" class="btn btn-secondary mr-2">Post</button>
+ <button type="submit" class="btn btn-secondary mr-2">{this.state.buttonTitle}</button>
{this.props.node && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}>Cancel</button>}
</div>
</div>
);
}
- handleCreateCommentSubmit(i: CommentForm, event) {
- WebSocketService.Instance.createComment(i.state.commentForm);
+ handleCommentSubmit(i: CommentForm, event) {
+ if (i.props.edit) {
+ WebSocketService.Instance.editComment(i.state.commentForm);
+ } else {
+ WebSocketService.Instance.createComment(i.state.commentForm);
+ }
+
i.state.commentForm.content = undefined;
i.setState(i.state);
event.target.reset();
}
handleCommentContentChange(i: CommentForm, event) {
- // TODO don't use setState, it triggers a re-render
i.state.commentForm.content = event.target.value;
+ i.setState(i.state);
}
handleReplyCancel(i: CommentForm, event) {