]> Untitled Git - lemmy.git/commitdiff
Adding a sidebar, title, and forum categories
authorDessalines <tyhou13@gmx.com>
Wed, 3 Apr 2019 23:01:20 +0000 (16:01 -0700)
committerDessalines <tyhou13@gmx.com>
Wed, 3 Apr 2019 23:01:20 +0000 (16:01 -0700)
- Adding a Sidebar component
- Starting on forum categories. #17
- Adding a Sidebar and title to community. Fixes #16

15 files changed:
server/migrations/2019-04-03-155205_create_community_view/up.sql
server/src/actions/category.rs [new file with mode: 0644]
server/src/actions/community.rs
server/src/actions/community_view.rs [new file with mode: 0644]
server/src/actions/mod.rs
server/src/actions/post_view.rs
server/src/actions/user.rs
server/src/bin/main.rs
server/src/websocket_server/server.rs
ui/src/components/community.tsx
ui/src/components/create-community.tsx
ui/src/components/post.tsx
ui/src/components/sidebar.tsx [new file with mode: 0644]
ui/src/interfaces.ts
ui/src/services/WebSocketService.ts

index e731b7f28c827eef319a90e6ed2193080bacbd29..7497250700e9b878dbbc65de9b9f450cdb2df231 100644 (file)
@@ -1,6 +1,7 @@
 create view community_view as 
 select *,
 (select name from user_ u where c.creator_id = u.id) as creator_name,
+(select name from category ct where c.category_id = ct.id) as category_name,
 (select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers,
 (select count(*) from post p where p.community_id = c.id) as number_of_posts
 from community c;
diff --git a/server/src/actions/category.rs b/server/src/actions/category.rs
new file mode 100644 (file)
index 0000000..8491f1e
--- /dev/null
@@ -0,0 +1,73 @@
+extern crate diesel;
+use schema::{category};
+use diesel::*;
+use diesel::result::Error;
+use serde::{Deserialize, Serialize};
+use {Crud};
+
+#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
+#[table_name="category"]
+pub struct Category {
+  pub id: i32,
+  pub name: String
+}
+
+#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
+#[table_name="category"]
+pub struct CategoryForm {
+  pub name: String,
+}
+
+impl Crud<CategoryForm> for Category {
+  fn read(conn: &PgConnection, category_id: i32) -> Result<Self, Error> {
+    use schema::category::dsl::*;
+    category.find(category_id)
+      .first::<Self>(conn)
+  }
+
+  fn delete(conn: &PgConnection, category_id: i32) -> Result<usize, Error> {
+    use schema::category::dsl::*;
+    diesel::delete(category.find(category_id))
+      .execute(conn)
+  }
+
+  fn create(conn: &PgConnection, new_category: &CategoryForm) -> Result<Self, Error> {
+    use schema::category::dsl::*;
+      insert_into(category)
+        .values(new_category)
+        .get_result::<Self>(conn)
+  }
+
+  fn update(conn: &PgConnection, category_id: i32, new_category: &CategoryForm) -> Result<Self, Error> {
+    use schema::category::dsl::*;
+    diesel::update(category.find(category_id))
+      .set(new_category)
+      .get_result::<Self>(conn)
+  }
+}
+
+impl Category {
+  pub fn list_all(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+    use schema::category::dsl::*;
+    category.load::<Self>(conn)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use establish_connection;
+  use super::*;
+  // use Crud;
+ #[test]
+  fn test_crud() {
+    let conn = establish_connection();
+
+    let categories = Category::list_all(&conn).unwrap();
+    let expected_first_category = Category {
+      id: 1,
+      name: "Discussion".into()
+    };
+
+    assert_eq!(expected_first_category, categories[0]);
+  }
+}
index a3fdbbd405e8d16b9037002e2067d10e6e0877a6..1c6343d0bfb9c7b1e22ae754a01a6e6438b25e3a 100644 (file)
@@ -125,13 +125,6 @@ impl Joinable<CommunityModeratorForm> for CommunityModerator {
   }
 }
 
-impl Community {
-  pub fn list_all(conn: &PgConnection) -> Result<Vec<Self>, Error> {
-    use schema::community::dsl::*;
-    community.load::<Self>(conn)
-  }
-}
-
 #[cfg(test)]
 mod tests {
   use establish_connection;
diff --git a/server/src/actions/community_view.rs b/server/src/actions/community_view.rs
new file mode 100644 (file)
index 0000000..9da6215
--- /dev/null
@@ -0,0 +1,51 @@
+extern crate diesel;
+use diesel::*;
+use diesel::result::Error;
+use serde::{Deserialize, Serialize};
+
+table! {
+  community_view (id) {
+    id -> Int4,
+    name -> Varchar,
+    title -> Varchar,
+    description -> Nullable<Text>,
+    category_id -> Int4,
+    creator_id -> Int4,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+    creator_name -> Varchar,
+    category_name -> Varchar,
+    number_of_subscribers -> BigInt,
+    number_of_posts -> BigInt,
+  }
+}
+
+#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize,QueryableByName,Clone)]
+#[table_name="community_view"]
+pub struct CommunityView {
+  pub id: i32,
+  pub name: String,
+  pub title: String,
+  pub description: Option<String>,
+  pub category_id: i32,
+  pub creator_id: i32,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub creator_name: String,
+  pub category_name: String,
+  pub number_of_subscribers: i64,
+  pub number_of_posts: i64
+}
+
+impl CommunityView {
+  pub fn read(conn: &PgConnection, from_community_id: i32) -> Result<Self, Error> {
+    use actions::community_view::community_view::dsl::*;
+    community_view.find(from_community_id).first::<Self>(conn)
+  }
+
+  pub fn list_all(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+    use actions::community_view::community_view::dsl::*;
+    community_view.load::<Self>(conn)
+  }
+}
+
index 8b4574d4abbb683f79ce4aa4203a242716dc6213..c17fd81ad5ad1425a06f8e2661f6a07d3105e54a 100644 (file)
@@ -4,3 +4,5 @@ pub mod post;
 pub mod comment;
 pub mod post_view;
 pub mod comment_view;
+pub mod category;
+pub mod community_view;
index a1d71de15441201165eff5ad760d84b9f8d87b39..f53a9f0c6a081a54a4cd3867f8de118a3bf03fbb 100644 (file)
@@ -100,7 +100,7 @@ impl PostView {
   }
 
 
-  pub fn get(conn: &PgConnection, from_post_id: i32, from_user_id: Option<i32>) -> Result<Self, Error> {
+  pub fn read(conn: &PgConnection, from_post_id: i32, from_user_id: Option<i32>) -> Result<Self, Error> {
 
     use actions::post_view::post_view::dsl::*;
     use diesel::prelude::*;
@@ -235,8 +235,8 @@ mod tests {
 
     let read_post_listings_with_user = PostView::list(&conn, ListingType::Community, ListingSortType::New, Some(inserted_community.id), Some(inserted_user.id), 10).unwrap();
     let read_post_listings_no_user = PostView::list(&conn, ListingType::Community, ListingSortType::New, Some(inserted_community.id), None, 10).unwrap();
-    let read_post_listing_no_user = PostView::get(&conn, inserted_post.id, None).unwrap();
-    let read_post_listing_with_user = PostView::get(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
+    let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap();
+    let read_post_listing_with_user = PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
 
     let like_removed = PostLike::remove(&conn, &post_like_form).unwrap();
     let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
index 70a021359ef3a9569388bd37f72a241b2004fe81..d646adcba599bd13062ddf624a6f838597529bc6 100644 (file)
@@ -117,7 +117,7 @@ mod tests {
     let conn = establish_connection();
     
     let new_user = UserForm {
-      name: "thom".into(),
+      name: "thommy".into(),
       fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
@@ -129,7 +129,7 @@ mod tests {
 
     let expected_user = User_ {
       id: inserted_user.id,
-      name: "thom".into(),
+      name: "thommy".into(),
       fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(),
index 08663f56309638500d66a803b5d4be3794ded872..fa0f532bb4880a8ed03808ee41207916f92593ce 100644 (file)
@@ -3,7 +3,7 @@ extern crate server;
 use std::time::{Instant, Duration};
 use server::actix::*;
 use server::actix_web::server::HttpServer;
-use server::actix_web::{fs, http, ws, App, Error, HttpRequest, HttpResponse};
+use server::actix_web::{ws, App, Error, HttpRequest, HttpResponse};
 
 use server::websocket_server::server::*;
 
index 4a2a746cf603b3c8271954f8def8513a4b70eb2e..e5e117ef1eb1db23fcd40bfee882ed8f271909f3 100644 (file)
@@ -17,10 +17,12 @@ use actions::post::*;
 use actions::comment::*;
 use actions::post_view::*;
 use actions::comment_view::*;
+use actions::category::*;
+use actions::community_view::*;
 
 #[derive(EnumString,ToString,Debug)]
 pub enum UserOperation {
-  Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity
+  Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity
 }
 
 #[derive(Serialize, Deserialize)]
@@ -103,7 +105,7 @@ pub struct CreateCommunity {
 #[derive(Serialize, Deserialize)]
 pub struct CreateCommunityResponse {
   op: String,
-  community: Community
+  community: CommunityView
 }
 
 #[derive(Serialize, Deserialize)]
@@ -112,7 +114,16 @@ pub struct ListCommunities;
 #[derive(Serialize, Deserialize)]
 pub struct ListCommunitiesResponse {
   op: String,
-  communities: Vec<Community>
+  communities: Vec<CommunityView>
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct ListCategories;
+
+#[derive(Serialize, Deserialize)]
+pub struct ListCategoriesResponse {
+  op: String,
+  categories: Vec<Category>
 }
 
 #[derive(Serialize, Deserialize)]
@@ -141,7 +152,8 @@ pub struct GetPost {
 pub struct GetPostResponse {
   op: String,
   post: PostView,
-  comments: Vec<CommentView>
+  comments: Vec<CommentView>,
+  community: CommunityView
 }
 
 #[derive(Serialize, Deserialize)]
@@ -167,7 +179,7 @@ pub struct GetCommunity {
 #[derive(Serialize, Deserialize)]
 pub struct GetCommunityResponse {
   op: String,
-  community: Community
+  community: CommunityView
 }
 
 #[derive(Serialize, Deserialize)]
@@ -333,7 +345,7 @@ impl Handler<Disconnect> for ChatServer {
     }
     // send message to other users
     // for room in rooms {
-      // self.send_room_message(room, "Someone disconnected", 0);
+    // self.send_room_message(room, "Someone disconnected", 0);
     // }
   }
 }
@@ -377,6 +389,10 @@ impl Handler<StandardMessage> for ChatServer {
         let list_communities: ListCommunities = ListCommunities;
         list_communities.perform(self, msg.id)
       },
+      UserOperation::ListCategories => {
+        let list_categories: ListCategories = ListCategories;
+        list_categories.perform(self, msg.id)
+      },
       UserOperation::CreatePost => {
         let create_post: CreatePost = serde_json::from_str(&data.to_string()).unwrap();
         create_post.perform(self, msg.id)
@@ -576,10 +592,12 @@ impl Perform for CreateCommunity {
       }
     };
 
+    let community_view = CommunityView::read(&conn, inserted_community.id).unwrap();
+
     serde_json::to_string(
       &CreateCommunityResponse {
         op: self.op_type().to_string(), 
-        community: inserted_community
+        community: community_view
       }
       )
       .unwrap()
@@ -595,7 +613,7 @@ impl Perform for ListCommunities {
 
     let conn = establish_connection();
 
-    let communities: Vec<Community> = Community::list_all(&conn).unwrap();
+    let communities: Vec<CommunityView> = CommunityView::list_all(&conn).unwrap();
 
     // Return the jwt
     serde_json::to_string(
@@ -608,6 +626,28 @@ impl Perform for ListCommunities {
   }
 }
 
+impl Perform for ListCategories {
+  fn op_type(&self) -> UserOperation {
+    UserOperation::ListCategories
+  }
+
+  fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> String {
+
+    let conn = establish_connection();
+
+    let categories: Vec<Category> = Category::list_all(&conn).unwrap();
+
+    // Return the jwt
+    serde_json::to_string(
+      &ListCategoriesResponse {
+        op: self.op_type().to_string(),
+        categories: categories
+      }
+      )
+      .unwrap()
+  }
+}
+
 impl Perform for CreatePost {
   fn op_type(&self) -> UserOperation {
     UserOperation::CreatePost
@@ -656,9 +696,9 @@ impl Perform for CreatePost {
         return self.error("Couldn't like post.");
       }
     };
-  
+
     // Refetch the view
-    let post_view = match PostView::get(&conn, inserted_post.id, Some(user_id)) {
+    let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) {
       Ok(post) => post,
       Err(_e) => {
         return self.error("Couldn't find Post");
@@ -700,7 +740,7 @@ impl Perform for GetPost {
       None => None
     };
 
-    let post_view = match PostView::get(&conn, self.id, user_id) {
+    let post_view = match PostView::read(&conn, self.id, user_id) {
       Ok(post) => post,
       Err(_e) => {
         return self.error("Couldn't find Post");
@@ -720,12 +760,15 @@ impl Perform for GetPost {
 
     let comments = CommentView::list(&conn, self.id, user_id).unwrap();
 
+    let community = CommunityView::read(&conn, post_view.community_id).unwrap();
+
     // Return the jwt
     serde_json::to_string(
       &GetPostResponse {
         op: self.op_type().to_string(),
         post: post_view,
-        comments: comments
+        comments: comments,
+        community: community
       }
       )
       .unwrap()
@@ -741,7 +784,7 @@ impl Perform for GetCommunity {
 
     let conn = establish_connection();
 
-    let community = match Community::read(&conn, self.id) {
+    let community_view = match CommunityView::read(&conn, self.id) {
       Ok(community) => community,
       Err(_e) => {
         return self.error("Couldn't find Community");
@@ -752,7 +795,7 @@ impl Perform for GetCommunity {
     serde_json::to_string(
       &GetCommunityResponse {
         op: self.op_type().to_string(),
-        community: community
+        community: community_view
       }
       )
       .unwrap()
@@ -828,7 +871,7 @@ impl Perform for CreateComment {
       }
       )
       .unwrap();
-    
+
     chat.send_room_message(self.post_id, &comment_sent_out, addr);
 
     comment_out
@@ -890,7 +933,7 @@ impl Perform for EditComment {
       }
       )
       .unwrap();
-    
+
     chat.send_room_message(self.post_id, &comment_sent_out, addr);
 
     comment_out
@@ -958,9 +1001,9 @@ impl Perform for CreateCommentLike {
       )
       .unwrap();
 
-      chat.send_room_message(self.post_id, &like_sent_out, addr);
+    chat.send_room_message(self.post_id, &like_sent_out, addr);
 
-      like_out
+    like_out
   }
 }
 
@@ -1049,7 +1092,7 @@ impl Perform for CreatePostLike {
       };
     }
 
-    let post_view = match PostView::get(&conn, self.post_id, Some(user_id)) {
+    let post_view = match PostView::read(&conn, self.post_id, Some(user_id)) {
       Ok(post) => post,
       Err(_e) => {
         return self.error("Couldn't find Post");
@@ -1066,7 +1109,7 @@ impl Perform for CreatePostLike {
       )
       .unwrap();
 
-      like_out
+    like_out
   }
 }
 
@@ -1104,7 +1147,7 @@ impl Perform for EditPost {
       }
     };
 
-    let post_view = PostView::get(&conn, self.edit_id, Some(user_id)).unwrap();
+    let post_view = PostView::read(&conn, self.edit_id, Some(user_id)).unwrap();
 
     let mut post_sent = post_view.clone();
     post_sent.my_vote = None;
@@ -1124,7 +1167,7 @@ impl Perform for EditPost {
       }
       )
       .unwrap();
-    
+
     chat.send_room_message(self.edit_id, &post_sent_out, addr);
 
     post_out
@@ -1255,7 +1298,7 @@ impl Perform for EditPost {
 //           )
 //         )
 //     };
-    
+
 //     MessageResult(
 //       Ok(
 //         CreateCommunityResponse {
index 5bef29bbdafc688fa30b4a13ce22eb6d9943cf06..820db90d6df4005bf640113f0d9598640e52d8e2 100644 (file)
@@ -6,7 +6,8 @@ import { UserOperation, Community as CommunityI, CommunityResponse, Post, GetPos
 import { WebSocketService, UserService } from '../services';
 import { MomentTime } from './moment-time';
 import { PostListing } from './post-listing';
-import { msgOp } from '../utils';
+import { Sidebar } from './sidebar';
+import { msgOp, mdToHtml } from '../utils';
 
 interface State {
   community: CommunityI;
@@ -21,6 +22,13 @@ export class Community extends Component<any, State> {
     community: {
       id: null,
       name: null,
+      title: null,
+      category_id: null,
+      category_name: null,
+      creator_id: null,
+      creator_name: null,
+      number_of_subscribers: null,
+      number_of_posts: null,
       published: null
     },
     posts: [],
@@ -70,10 +78,8 @@ export class Community extends Component<any, State> {
             }
           </div>
           <div class="col-12 col-sm-2 col-lg-3">
-            Sidebar
+            <Sidebar community={this.state.community} />
           </div>
-      
-          
         </div>
       </div>
     )
index 7e56fcdabe086ea93ae261574b60a3875bd79850..c5149f0a8c76a43575037db96122d73cef4c6bf3 100644 (file)
@@ -1,7 +1,7 @@
 import { Component, linkEvent } from 'inferno';
 import { Subscription } from "rxjs";
 import { retryWhen, delay, take } from 'rxjs/operators';
-import { CommunityForm, UserOperation } from '../interfaces';
+import { CommunityForm, UserOperation, Category, ListCategoriesResponse } from '../interfaces';
 import { WebSocketService, UserService } from '../services';
 import { msgOp } from '../utils';
 
@@ -9,6 +9,7 @@ import { Community } from '../interfaces';
 
 interface State {
   communityForm: CommunityForm;
+  categories: Array<Category>;
 }
 
 export class CreateCommunity extends Component<any, State> {
@@ -17,14 +18,17 @@ export class CreateCommunity extends Component<any, State> {
   private emptyState: State = {
     communityForm: {
       name: null,
-    }
+      title: null,
+      category_id: null
+    },
+    categories: []
   }
 
   constructor(props, context) {
     super(props, context);
 
     this.state = this.emptyState;
-    
+
     this.subscription = WebSocketService.Instance.subject
       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
       .subscribe(
@@ -32,6 +36,8 @@ export class CreateCommunity extends Component<any, State> {
         (err) => console.error(err),
         () => console.log("complete")
       );
+
+    WebSocketService.Instance.listCategories();
   }
 
   componentWillUnmount() {
@@ -61,6 +67,28 @@ export class CreateCommunity extends Component<any, State> {
               <input type="text" class="form-control" value={this.state.communityForm.name} onInput={linkEvent(this, this.handleCommunityNameChange)} required minLength={3} />
             </div>
           </div>
+          <div class="form-group row">
+            <label class="col-sm-2 col-form-label">Title / Headline</label>
+            <div class="col-sm-10">
+              <input type="text" value={this.state.communityForm.title} onInput={linkEvent(this, this.handleCommunityTitleChange)} class="form-control" required minLength={3} />
+            </div>
+          </div>
+          <div class="form-group row">
+            <label class="col-sm-2 col-form-label">Description / Sidebar</label>
+            <div class="col-sm-10">
+              <textarea value={this.state.communityForm.description} onInput={linkEvent(this, this.handleCommunityDescriptionChange)} class="form-control" rows={6} />
+            </div>
+          </div>
+          <div class="form-group row">
+            <label class="col-sm-2 col-form-label">Category</label>
+            <div class="col-sm-10">
+              <select class="form-control" value={this.state.communityForm.category_id} onInput={linkEvent(this, this.handleCommunityCategoryChange)}>
+                {this.state.categories.map(category =>
+                  <option value={category.id}>{category.name}</option>
+                )}
+              </select>
+            </div>
+          </div>
           <div class="form-group row">
             <div class="col-sm-10">
               <button type="submit" class="btn btn-secondary">Create</button>
@@ -70,7 +98,7 @@ export class CreateCommunity extends Component<any, State> {
       </div>
     );
   }
-  
+
   handleCreateCommunitySubmit(i: CreateCommunity, event) {
     event.preventDefault();
     WebSocketService.Instance.createCommunity(i.state.communityForm);
@@ -78,6 +106,22 @@ export class CreateCommunity extends Component<any, State> {
 
   handleCommunityNameChange(i: CreateCommunity, event) {
     i.state.communityForm.name = event.target.value;
+    i.setState(i.state);
+  }
+
+  handleCommunityTitleChange(i: CreateCommunity, event) {
+    i.state.communityForm.title = event.target.value;
+    i.setState(i.state);
+  }
+
+  handleCommunityDescriptionChange(i: CreateCommunity, event) {
+    i.state.communityForm.description = event.target.value;
+    i.setState(i.state);
+  }
+
+  handleCommunityCategoryChange(i: CreateCommunity, event) {
+    i.state.communityForm.category_id = Number(event.target.value);
+    i.setState(i.state);
   }
 
   parseMessage(msg: any) {
@@ -86,11 +130,14 @@ export class CreateCommunity extends Component<any, State> {
     if (msg.error) {
       alert(msg.error);
       return;
-    } else {
-      if (op == UserOperation.CreateCommunity) {
-        let community: Community = msg.community;
-        this.props.history.push(`/community/${community.id}`);
-      }
+    } else if (op == UserOperation.ListCategories){
+      let res: ListCategoriesResponse = msg;
+      this.state.categories = res.categories;
+      this.state.communityForm.category_id = res.categories[0].id;
+      this.setState(this.state);
+    } else if (op == UserOperation.CreateCommunity) {
+      let community: Community = msg.community;
+      this.props.history.push(`/community/${community.id}`);
     }
   }
 
index 68ba96346f5df43c1052709e8d6fdb047881a3bc..457b286e00acc5c7f71a6e6f8d161aa4d090ef93 100644 (file)
@@ -2,11 +2,12 @@ import { Component, linkEvent } from 'inferno';
 import { Link } from 'inferno-router';
 import { Subscription } from "rxjs";
 import { retryWhen, delay, take } from 'rxjs/operators';
-import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CreateCommentLikeResponse, CommentSortType, CreatePostLikeResponse } from '../interfaces';
+import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CommentSortType, CreatePostLikeResponse } from '../interfaces';
 import { WebSocketService, UserService } from '../services';
 import { msgOp, hotRank,mdToHtml } from '../utils';
 import { MomentTime } from './moment-time';
 import { PostListing } from './post-listing';
+import { Sidebar } from './sidebar';
 import * as autosize from 'autosize';
 
 interface CommentNodeI {
@@ -14,19 +15,21 @@ interface CommentNodeI {
   children?: Array<CommentNodeI>;
 };
 
-interface State {
+interface PostState {
   post: PostI;
   comments: Array<Comment>;
   commentSort: CommentSortType;
+  community: Community;
 }
 
-export class Post extends Component<any, State> {
+export class Post extends Component<any, PostState> {
 
   private subscription: Subscription;
-  private emptyState: State = {
+  private emptyState: PostState = {
     post: null,
     comments: [],
-    commentSort: CommentSortType.Hot
+    commentSort: CommentSortType.Hot,
+    community: null,
   }
 
   constructor(props, context) {
@@ -115,8 +118,7 @@ export class Post extends Component<any, State> {
   sidebar() {
     return ( 
       <div class="sticky-top">
-        <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>
+        <Sidebar community={this.state.community} />
       </div>
     );
   }
@@ -185,6 +187,7 @@ export class Post extends Component<any, State> {
       let res: GetPostResponse = msg;
       this.state.post = res.post;
       this.state.comments = res.comments;
+      this.state.community = res.community;
       this.setState(this.state);
     } else if (op == UserOperation.CreateComment) {
       let res: CommentResponse = msg;
@@ -198,7 +201,7 @@ export class Post extends Component<any, State> {
       this.setState(this.state);
     }
     else if (op == UserOperation.CreateCommentLike) {
-      let res: CreateCommentLikeResponse = msg;
+      let res: CommentResponse = msg;
       let found: Comment = this.state.comments.find(c => c.id === res.comment.id);
       found.score = res.comment.score;
       found.upvotes = res.comment.upvotes;
diff --git a/ui/src/components/sidebar.tsx b/ui/src/components/sidebar.tsx
new file mode 100644 (file)
index 0000000..8a88b87
--- /dev/null
@@ -0,0 +1,33 @@
+import { Component, linkEvent } from 'inferno';
+import { Community } from '../interfaces';
+import { mdToHtml } from '../utils';
+
+interface SidebarProps {
+  community: Community;
+}
+
+interface SidebarState {
+}
+
+export class Sidebar extends Component<SidebarProps, SidebarState> {
+
+  constructor(props, context) {
+    super(props, context);
+  }
+
+
+  render() {
+    let community = this.props.community;
+    return (
+      <div>
+        <h4>{community.title}</h4>
+        <div><button type="button" class="btn btn-secondary mb-2">Subscribe</button></div>
+        <div className="badge badge-light">{community.category_name}</div>
+        <div>{community.number_of_subscribers} Subscribers</div>
+        <div>{community.number_of_posts} Posts</div>
+        <hr />
+        {community.description && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(community.description)} />}
+      </div>
+    );
+  }
+}
index e21edf77eb5bc5a78247a21382a7a07013848437..5d5ea12a0d27fe958a92460b34308f229e1e3a02 100644 (file)
@@ -1,5 +1,5 @@
 export enum UserOperation {
-  Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity
+  Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, EditCommunity
 }
 
 export interface User {
@@ -11,12 +11,23 @@ export interface User {
 export interface Community {
   id: number;
   name: string;
+  title: string;
+  description?: string;
+  creator_id: number;
+  creator_name: string;
+  category_id: number;
+  category_name: string;
+  number_of_subscribers: number;
+  number_of_posts: number;
   published: string;
   updated?: string;
 }
 
 export interface CommunityForm {
   name: string;
+  title: string;
+  description?: string,
+  category_id: number,
   auth?: string;
 }
 
@@ -30,6 +41,11 @@ export interface ListCommunitiesResponse {
   communities: Array<Community>;
 }
 
+export interface ListCategoriesResponse {
+  op: string;
+  categories: Array<Category>;
+}
+
 export interface Post {
   user_id?: number;
   my_vote?: number;
@@ -64,6 +80,7 @@ export interface GetPostResponse {
   op: string;
   post: Post;
   comments: Array<Comment>;
+  community: Community;
 }
 
 export interface PostResponse {
@@ -130,6 +147,11 @@ export interface CreatePostLikeResponse {
   post: Post;
 }
 
+export interface Category {
+  id: number;
+  name: string;
+}
+
 export interface LoginForm {
   username_or_email: string;
   password: string;
index c1fdb73bd32e04a3dc7237feed8f88aa7aeb07e3..c99cfcfa34e8a4caba36fd62a873e6ba2e09e274 100644 (file)
@@ -41,6 +41,10 @@ export class WebSocketService {
     this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, undefined));
   }
 
+  public listCategories() {
+    this.subject.next(this.wsSendWrapper(UserOperation.ListCategories, undefined));
+  }
+
   public createPost(postForm: PostForm) {
     this.setAuth(postForm);
     this.subject.next(this.wsSendWrapper(UserOperation.CreatePost, postForm));