mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-23 03:11:32 +00:00
Adding a sidebar, title, and forum categories
- Adding a Sidebar component - Starting on forum categories. #17 - Adding a Sidebar and title to community. Fixes #16
This commit is contained in:
parent
e690d6c470
commit
927cb6d356
15 changed files with 336 additions and 58 deletions
|
@ -1,6 +1,7 @@
|
||||||
create view community_view as
|
create view community_view as
|
||||||
select *,
|
select *,
|
||||||
(select name from user_ u where c.creator_id = u.id) as creator_name,
|
(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 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
|
(select count(*) from post p where p.community_id = c.id) as number_of_posts
|
||||||
from community c;
|
from community c;
|
||||||
|
|
73
server/src/actions/category.rs
Normal file
73
server/src/actions/category.rs
Normal file
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use establish_connection;
|
use establish_connection;
|
||||||
|
|
51
server/src/actions/community_view.rs
Normal file
51
server/src/actions/community_view.rs
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,3 +4,5 @@ pub mod post;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod post_view;
|
pub mod post_view;
|
||||||
pub mod comment_view;
|
pub mod comment_view;
|
||||||
|
pub mod category;
|
||||||
|
pub mod community_view;
|
||||||
|
|
|
@ -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 actions::post_view::post_view::dsl::*;
|
||||||
use diesel::prelude::*;
|
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_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_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_no_user = PostView::read(&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_with_user = PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
|
||||||
|
|
||||||
let like_removed = PostLike::remove(&conn, &post_like_form).unwrap();
|
let like_removed = PostLike::remove(&conn, &post_like_form).unwrap();
|
||||||
let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
|
let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
|
||||||
|
|
|
@ -117,7 +117,7 @@ mod tests {
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
let new_user = UserForm {
|
let new_user = UserForm {
|
||||||
name: "thom".into(),
|
name: "thommy".into(),
|
||||||
fedi_name: "rrf".into(),
|
fedi_name: "rrf".into(),
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "nope".into(),
|
password_encrypted: "nope".into(),
|
||||||
|
@ -129,7 +129,7 @@ mod tests {
|
||||||
|
|
||||||
let expected_user = User_ {
|
let expected_user = User_ {
|
||||||
id: inserted_user.id,
|
id: inserted_user.id,
|
||||||
name: "thom".into(),
|
name: "thommy".into(),
|
||||||
fedi_name: "rrf".into(),
|
fedi_name: "rrf".into(),
|
||||||
preferred_username: None,
|
preferred_username: None,
|
||||||
password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(),
|
password_encrypted: "$2y$12$YXpNpYsdfjmed.QlYLvw4OfTCgyKUnKHc/V8Dgcf9YcVKHPaYXYYy".into(),
|
||||||
|
|
|
@ -3,7 +3,7 @@ extern crate server;
|
||||||
use std::time::{Instant, Duration};
|
use std::time::{Instant, Duration};
|
||||||
use server::actix::*;
|
use server::actix::*;
|
||||||
use server::actix_web::server::HttpServer;
|
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::*;
|
use server::websocket_server::server::*;
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,12 @@ use actions::post::*;
|
||||||
use actions::comment::*;
|
use actions::comment::*;
|
||||||
use actions::post_view::*;
|
use actions::post_view::*;
|
||||||
use actions::comment_view::*;
|
use actions::comment_view::*;
|
||||||
|
use actions::category::*;
|
||||||
|
use actions::community_view::*;
|
||||||
|
|
||||||
#[derive(EnumString,ToString,Debug)]
|
#[derive(EnumString,ToString,Debug)]
|
||||||
pub enum UserOperation {
|
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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -103,7 +105,7 @@ pub struct CreateCommunity {
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct CreateCommunityResponse {
|
pub struct CreateCommunityResponse {
|
||||||
op: String,
|
op: String,
|
||||||
community: Community
|
community: CommunityView
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -112,7 +114,16 @@ pub struct ListCommunities;
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct ListCommunitiesResponse {
|
pub struct ListCommunitiesResponse {
|
||||||
op: String,
|
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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -141,7 +152,8 @@ pub struct GetPost {
|
||||||
pub struct GetPostResponse {
|
pub struct GetPostResponse {
|
||||||
op: String,
|
op: String,
|
||||||
post: PostView,
|
post: PostView,
|
||||||
comments: Vec<CommentView>
|
comments: Vec<CommentView>,
|
||||||
|
community: CommunityView
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -167,7 +179,7 @@ pub struct GetCommunity {
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GetCommunityResponse {
|
pub struct GetCommunityResponse {
|
||||||
op: String,
|
op: String,
|
||||||
community: Community
|
community: CommunityView
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -377,6 +389,10 @@ impl Handler<StandardMessage> for ChatServer {
|
||||||
let list_communities: ListCommunities = ListCommunities;
|
let list_communities: ListCommunities = ListCommunities;
|
||||||
list_communities.perform(self, msg.id)
|
list_communities.perform(self, msg.id)
|
||||||
},
|
},
|
||||||
|
UserOperation::ListCategories => {
|
||||||
|
let list_categories: ListCategories = ListCategories;
|
||||||
|
list_categories.perform(self, msg.id)
|
||||||
|
},
|
||||||
UserOperation::CreatePost => {
|
UserOperation::CreatePost => {
|
||||||
let create_post: CreatePost = serde_json::from_str(&data.to_string()).unwrap();
|
let create_post: CreatePost = serde_json::from_str(&data.to_string()).unwrap();
|
||||||
create_post.perform(self, msg.id)
|
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(
|
serde_json::to_string(
|
||||||
&CreateCommunityResponse {
|
&CreateCommunityResponse {
|
||||||
op: self.op_type().to_string(),
|
op: self.op_type().to_string(),
|
||||||
community: inserted_community
|
community: community_view
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -595,7 +613,7 @@ impl Perform for ListCommunities {
|
||||||
|
|
||||||
let conn = establish_connection();
|
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
|
// Return the jwt
|
||||||
serde_json::to_string(
|
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 {
|
impl Perform for CreatePost {
|
||||||
fn op_type(&self) -> UserOperation {
|
fn op_type(&self) -> UserOperation {
|
||||||
UserOperation::CreatePost
|
UserOperation::CreatePost
|
||||||
|
@ -658,7 +698,7 @@ impl Perform for CreatePost {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Refetch the view
|
// 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,
|
Ok(post) => post,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
return self.error("Couldn't find Post");
|
return self.error("Couldn't find Post");
|
||||||
|
@ -700,7 +740,7 @@ impl Perform for GetPost {
|
||||||
None => None
|
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,
|
Ok(post) => post,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
return self.error("Couldn't find Post");
|
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 comments = CommentView::list(&conn, self.id, user_id).unwrap();
|
||||||
|
|
||||||
|
let community = CommunityView::read(&conn, post_view.community_id).unwrap();
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
serde_json::to_string(
|
serde_json::to_string(
|
||||||
&GetPostResponse {
|
&GetPostResponse {
|
||||||
op: self.op_type().to_string(),
|
op: self.op_type().to_string(),
|
||||||
post: post_view,
|
post: post_view,
|
||||||
comments: comments
|
comments: comments,
|
||||||
|
community: community
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -741,7 +784,7 @@ impl Perform for GetCommunity {
|
||||||
|
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
let community = match Community::read(&conn, self.id) {
|
let community_view = match CommunityView::read(&conn, self.id) {
|
||||||
Ok(community) => community,
|
Ok(community) => community,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
return self.error("Couldn't find Community");
|
return self.error("Couldn't find Community");
|
||||||
|
@ -752,7 +795,7 @@ impl Perform for GetCommunity {
|
||||||
serde_json::to_string(
|
serde_json::to_string(
|
||||||
&GetCommunityResponse {
|
&GetCommunityResponse {
|
||||||
op: self.op_type().to_string(),
|
op: self.op_type().to_string(),
|
||||||
community: community
|
community: community_view
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -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,
|
Ok(post) => post,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
return self.error("Couldn't find Post");
|
return self.error("Couldn't find Post");
|
||||||
|
@ -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();
|
let mut post_sent = post_view.clone();
|
||||||
post_sent.my_vote = None;
|
post_sent.my_vote = None;
|
||||||
|
|
|
@ -6,7 +6,8 @@ import { UserOperation, Community as CommunityI, CommunityResponse, Post, GetPos
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { MomentTime } from './moment-time';
|
import { MomentTime } from './moment-time';
|
||||||
import { PostListing } from './post-listing';
|
import { PostListing } from './post-listing';
|
||||||
import { msgOp } from '../utils';
|
import { Sidebar } from './sidebar';
|
||||||
|
import { msgOp, mdToHtml } from '../utils';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
community: CommunityI;
|
community: CommunityI;
|
||||||
|
@ -21,6 +22,13 @@ export class Community extends Component<any, State> {
|
||||||
community: {
|
community: {
|
||||||
id: null,
|
id: null,
|
||||||
name: 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
|
published: null
|
||||||
},
|
},
|
||||||
posts: [],
|
posts: [],
|
||||||
|
@ -70,10 +78,8 @@ export class Community extends Component<any, State> {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-sm-2 col-lg-3">
|
<div class="col-12 col-sm-2 col-lg-3">
|
||||||
Sidebar
|
<Sidebar community={this.state.community} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Subscription } from "rxjs";
|
import { Subscription } from "rxjs";
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
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 { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp } from '../utils';
|
import { msgOp } from '../utils';
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import { Community } from '../interfaces';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
communityForm: CommunityForm;
|
communityForm: CommunityForm;
|
||||||
|
categories: Array<Category>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateCommunity extends Component<any, State> {
|
export class CreateCommunity extends Component<any, State> {
|
||||||
|
@ -17,7 +18,10 @@ export class CreateCommunity extends Component<any, State> {
|
||||||
private emptyState: State = {
|
private emptyState: State = {
|
||||||
communityForm: {
|
communityForm: {
|
||||||
name: null,
|
name: null,
|
||||||
}
|
title: null,
|
||||||
|
category_id: null
|
||||||
|
},
|
||||||
|
categories: []
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
|
@ -32,6 +36,8 @@ export class CreateCommunity extends Component<any, State> {
|
||||||
(err) => console.error(err),
|
(err) => console.error(err),
|
||||||
() => console.log("complete")
|
() => console.log("complete")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
WebSocketService.Instance.listCategories();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
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} />
|
<input type="text" class="form-control" value={this.state.communityForm.name} onInput={linkEvent(this, this.handleCommunityNameChange)} required minLength={3} />
|
||||||
</div>
|
</div>
|
||||||
</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="form-group row">
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<button type="submit" class="btn btn-secondary">Create</button>
|
<button type="submit" class="btn btn-secondary">Create</button>
|
||||||
|
@ -78,6 +106,22 @@ export class CreateCommunity extends Component<any, State> {
|
||||||
|
|
||||||
handleCommunityNameChange(i: CreateCommunity, event) {
|
handleCommunityNameChange(i: CreateCommunity, event) {
|
||||||
i.state.communityForm.name = event.target.value;
|
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) {
|
parseMessage(msg: any) {
|
||||||
|
@ -86,12 +130,15 @@ export class CreateCommunity extends Component<any, State> {
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
alert(msg.error);
|
alert(msg.error);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else if (op == UserOperation.ListCategories){
|
||||||
if (op == UserOperation.CreateCommunity) {
|
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;
|
let community: Community = msg.community;
|
||||||
this.props.history.push(`/community/${community.id}`);
|
this.props.history.push(`/community/${community.id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@ import { Component, linkEvent } from 'inferno';
|
||||||
import { Link } from 'inferno-router';
|
import { Link } from 'inferno-router';
|
||||||
import { Subscription } from "rxjs";
|
import { Subscription } from "rxjs";
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
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 { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp, hotRank,mdToHtml } from '../utils';
|
import { msgOp, hotRank,mdToHtml } from '../utils';
|
||||||
import { MomentTime } from './moment-time';
|
import { MomentTime } from './moment-time';
|
||||||
import { PostListing } from './post-listing';
|
import { PostListing } from './post-listing';
|
||||||
|
import { Sidebar } from './sidebar';
|
||||||
import * as autosize from 'autosize';
|
import * as autosize from 'autosize';
|
||||||
|
|
||||||
interface CommentNodeI {
|
interface CommentNodeI {
|
||||||
|
@ -14,19 +15,21 @@ interface CommentNodeI {
|
||||||
children?: Array<CommentNodeI>;
|
children?: Array<CommentNodeI>;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface State {
|
interface PostState {
|
||||||
post: PostI;
|
post: PostI;
|
||||||
comments: Array<Comment>;
|
comments: Array<Comment>;
|
||||||
commentSort: CommentSortType;
|
commentSort: CommentSortType;
|
||||||
|
community: Community;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Post extends Component<any, State> {
|
export class Post extends Component<any, PostState> {
|
||||||
|
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
private emptyState: State = {
|
private emptyState: PostState = {
|
||||||
post: null,
|
post: null,
|
||||||
comments: [],
|
comments: [],
|
||||||
commentSort: CommentSortType.Hot
|
commentSort: CommentSortType.Hot,
|
||||||
|
community: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
|
@ -115,8 +118,7 @@ export class Post extends Component<any, State> {
|
||||||
sidebar() {
|
sidebar() {
|
||||||
return (
|
return (
|
||||||
<div class="sticky-top">
|
<div class="sticky-top">
|
||||||
<h5>Sidebar</h5>
|
<Sidebar community={this.state.community} />
|
||||||
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -185,6 +187,7 @@ export class Post extends Component<any, State> {
|
||||||
let res: GetPostResponse = msg;
|
let res: GetPostResponse = msg;
|
||||||
this.state.post = res.post;
|
this.state.post = res.post;
|
||||||
this.state.comments = res.comments;
|
this.state.comments = res.comments;
|
||||||
|
this.state.community = res.community;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateComment) {
|
} else if (op == UserOperation.CreateComment) {
|
||||||
let res: CommentResponse = msg;
|
let res: CommentResponse = msg;
|
||||||
|
@ -198,7 +201,7 @@ export class Post extends Component<any, State> {
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
else if (op == UserOperation.CreateCommentLike) {
|
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);
|
let found: Comment = this.state.comments.find(c => c.id === res.comment.id);
|
||||||
found.score = res.comment.score;
|
found.score = res.comment.score;
|
||||||
found.upvotes = res.comment.upvotes;
|
found.upvotes = res.comment.upvotes;
|
||||||
|
|
33
ui/src/components/sidebar.tsx
Normal file
33
ui/src/components/sidebar.tsx
Normal file
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
export enum UserOperation {
|
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 {
|
export interface User {
|
||||||
|
@ -11,12 +11,23 @@ export interface User {
|
||||||
export interface Community {
|
export interface Community {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
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;
|
published: string;
|
||||||
updated?: string;
|
updated?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommunityForm {
|
export interface CommunityForm {
|
||||||
name: string;
|
name: string;
|
||||||
|
title: string;
|
||||||
|
description?: string,
|
||||||
|
category_id: number,
|
||||||
auth?: string;
|
auth?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +41,11 @@ export interface ListCommunitiesResponse {
|
||||||
communities: Array<Community>;
|
communities: Array<Community>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ListCategoriesResponse {
|
||||||
|
op: string;
|
||||||
|
categories: Array<Category>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Post {
|
export interface Post {
|
||||||
user_id?: number;
|
user_id?: number;
|
||||||
my_vote?: number;
|
my_vote?: number;
|
||||||
|
@ -64,6 +80,7 @@ export interface GetPostResponse {
|
||||||
op: string;
|
op: string;
|
||||||
post: Post;
|
post: Post;
|
||||||
comments: Array<Comment>;
|
comments: Array<Comment>;
|
||||||
|
community: Community;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostResponse {
|
export interface PostResponse {
|
||||||
|
@ -130,6 +147,11 @@ export interface CreatePostLikeResponse {
|
||||||
post: Post;
|
post: Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Category {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LoginForm {
|
export interface LoginForm {
|
||||||
username_or_email: string;
|
username_or_email: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
|
|
@ -41,6 +41,10 @@ export class WebSocketService {
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, undefined));
|
this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, undefined));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public listCategories() {
|
||||||
|
this.subject.next(this.wsSendWrapper(UserOperation.ListCategories, undefined));
|
||||||
|
}
|
||||||
|
|
||||||
public createPost(postForm: PostForm) {
|
public createPost(postForm: PostForm) {
|
||||||
this.setAuth(postForm);
|
this.setAuth(postForm);
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.CreatePost, postForm));
|
this.subject.next(this.wsSendWrapper(UserOperation.CreatePost, postForm));
|
||||||
|
|
Loading…
Reference in a new issue