]> Untitled Git - lemmy.git/commitdiff
Adding comment editing
authorDessalines <tyhou13@gmx.com>
Fri, 29 Mar 2019 04:56:23 +0000 (21:56 -0700)
committerDessalines <tyhou13@gmx.com>
Fri, 29 Mar 2019 04:56:23 +0000 (21:56 -0700)
- Fixes #8

server/src/actions/comment.rs
server/src/websocket_server/server.rs
ui/src/components/post.tsx
ui/src/interfaces.ts
ui/src/services/UserService.ts
ui/src/services/WebSocketService.ts

index 6a1a4671e0efbb6ab803851a60d872bf3734c8ba..089c384cdc812c486c1ca61ea3975fae1c645fa4 100644 (file)
@@ -174,7 +174,7 @@ impl CommentView {
       post_id: comment.post_id,
       attributed_to: comment.attributed_to.to_owned(),
       published: comment.published,
-      updated: None,
+      updated: comment.updated,
       upvotes: upvotes,
       score: score,
       downvotes: downvotes,
index cb2b619bf197f0b92f884fc2280620400108269d..e81202065e8cbf7887adc8806cd1638b0ba15d7f 100644 (file)
@@ -6,12 +6,11 @@ use actix::prelude::*;
 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::*;
@@ -20,7 +19,7 @@ use actions::comment::*;
 
 #[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
 }
 
 
@@ -178,6 +177,7 @@ pub struct GetCommunityResponse {
 pub struct CreateComment {
   content: String,
   parent_id: Option<i32>,
+  edit_id: Option<i32>,
   post_id: i32,
   auth: String
 }
@@ -189,6 +189,21 @@ pub struct CreateCommentResponse {
 }
 
 
+#[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,
@@ -360,6 +375,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::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)
@@ -483,7 +502,9 @@ impl Perform for CreateCommunity {
     };
 
     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(),
@@ -499,7 +520,7 @@ impl Perform for CreateCommunity {
 
     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) {
@@ -558,15 +579,16 @@ impl Perform for CreatePost {
     };
 
     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
     };
 
@@ -603,9 +625,10 @@ impl Perform for GetPost {
       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
         }
@@ -692,8 +715,9 @@ impl Perform for CreateComment {
     };
 
     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(),
@@ -729,7 +753,6 @@ impl Perform for CreateComment {
 
     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;
 
@@ -741,7 +764,6 @@ impl Perform for CreateComment {
       )
       .unwrap();
 
-
     let comment_sent_out = serde_json::to_string(
       &CreateCommentLikeResponse {
         op: self.op_type().to_string(), 
@@ -756,6 +778,75 @@ impl Perform for CreateComment {
   }
 }
 
+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 {
@@ -774,8 +865,9 @@ impl Perform for CreateCommentLike {
     };
 
     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,
index 2a780cf767c6a53cfd86f0d5f858acaba045a7f1..c5c8a53fc20334fdee61b756890cfc4d5b2074e9 100644 (file)
@@ -91,7 +91,7 @@ export class Post extends Component<any, State> {
   newComments() {
     return (
       <div class="sticky-top">
-        <h4>New Comments</h4>
+        <h5>New Comments</h5>
         {this.state.comments.map(comment => 
           <CommentNodes nodes={[{comment: comment}]} noIndent />
         )}
@@ -102,7 +102,7 @@ export class Post extends Component<any, State> {
   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>
     );
@@ -155,7 +155,14 @@ 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) {
+    } 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;
@@ -163,7 +170,6 @@ export class Post extends Component<any, State> {
       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);
     }
 
@@ -198,6 +204,7 @@ export class CommentNodes extends Component<CommentNodesProps, CommentNodesState
 
 interface CommentNodeState {
   showReply: boolean;
+  showEdit: boolean;
 }
 
 interface CommentNodeProps {
@@ -208,7 +215,8 @@ interface CommentNodeProps {
 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
 
   private emptyState: CommentNodeState = {
-    showReply: false
+    showReply: false,
+    showEdit: false
   }
 
   constructor(props, context) {
@@ -246,15 +254,25 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
               <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}/>}
@@ -262,8 +280,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     )
   }
 
-  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) {
@@ -271,11 +289,18 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     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 = {
@@ -300,10 +325,12 @@ interface CommentFormProps {
   postId?: number;
   node?: CommentNodeI;
   onReplyCancel?();
+  edit?: boolean;
 }
 
 interface CommentFormState {
   commentForm: CommentFormI;
+  buttonTitle: string;
 }
 
 export class CommentForm extends Component<CommentFormProps, CommentFormState> {
@@ -312,27 +339,33 @@ 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 />
@@ -340,7 +373,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
           </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>
@@ -349,8 +382,13 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
     );
   }
 
-  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();
@@ -360,8 +398,8 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
   }
 
   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) {
index d499eb0a7554e7c20a0f23b5888f924c7510a764..c89a254ec3f8a8d92b987476d2364d2583891436 100644 (file)
@@ -1,9 +1,10 @@
 export enum UserOperation {
-  Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, CreateCommentLike
+  Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike
 }
 
 export interface User {
   id: number;
+  iss: string;
   username: string;
 }
 
@@ -73,6 +74,7 @@ export interface CommentForm {
   content: string;
   post_id: number;
   parent_id?: number;
+  edit_id?: number;
   auth: string;
 }
 
index 42411f8832be419ee4ff6a8cae41ab1fb1a51c3e..c090aebce253af720255802fb6d207d061bb5e5b 100644 (file)
@@ -42,6 +42,11 @@ export class UserService {
   private setUser(jwt: string) {
     this.user = jwt_decode(jwt);
     this.sub.next(this.user);
+    console.log(this.user);
+  }
+
+  public get fediUserId(): string {
+    return `${this.user.iss}/${this.user.username}`;
   }
 
   public static get Instance(){
index ed08fa1ec65284191ff48943798ddbe102cc5934..b35d97a1fd4cf05da147be284271c62f70e5f8a9 100644 (file)
@@ -14,7 +14,7 @@ export class WebSocketService {
 
     // Even tho this isn't used, its necessary to not keep reconnecting
     this.subject
-      .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
+      .pipe(retryWhen(errors => errors.pipe(delay(60000), take(999))))
       .subscribe();
 
     console.log(`Connected to ${wsUri}`);
@@ -60,6 +60,11 @@ export class WebSocketService {
     this.subject.next(this.wsSendWrapper(UserOperation.CreateComment, commentForm));
   }
 
+  public editComment(commentForm: CommentForm) {
+    this.setAuth(commentForm);
+    this.subject.next(this.wsSendWrapper(UserOperation.EditComment, commentForm));
+  }
+
   public likeComment(form: CommentLikeForm) {
     this.setAuth(form);
     this.subject.next(this.wsSendWrapper(UserOperation.CreateCommentLike, form));