Merge branch 'dev' into moderation

This commit is contained in:
Dessalines 2019-04-11 07:15:22 -07:00
commit 1bf0dfde20
22 changed files with 251 additions and 86 deletions

View File

@ -1,12 +1,39 @@
FROM node:10-jessie as node
#If encounter Invalid cross-device error -run on host 'echo N | sudo tee /sys/module/overlay/parameters/metacopy'
COPY ui /app/ui
RUN cd /app/ui && yarn && yarn build
WORKDIR /app/ui
RUN yarn
RUN yarn build
FROM rust:1.33 as rust
COPY server /app/server
# create a new empty shell project
WORKDIR /app
RUN USER=root cargo new server
WORKDIR /app/server
# copy over your manifests
COPY server/Cargo.toml server/Cargo.lock ./
# this build step will cache your dependencies
RUN mkdir -p ./src/bin \
&& echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs
RUN cargo build --release --bin lemmy
RUN ls ./target/release/.fingerprint/
RUN rm -r ./target/release/.fingerprint/server-*
# copy your source tree
# RUN rm -rf ./src/
COPY server/src ./src/
COPY server/migrations ./migrations/
# build for release
RUN cargo build --frozen --release --bin lemmy
RUN mv /app/server/target/release/lemmy /app/lemmy
# The output image
# FROM debian:stable-slim
# RUN apt-get -y update && apt-get install -y postgresql-client
# COPY --from=rust /app/server/target/release/lemmy /app/lemmy
COPY --from=node /app/ui/dist /app/dist
RUN cd /app/server && cargo build --release
RUN mv /app/server/target/release/lemmy /app/
WORKDIR /app/
EXPOSE 8536

View File

@ -10,9 +10,9 @@ services:
POSTGRES_DB: rrr
healthcheck:
test: ["CMD-SHELL", "pg_isready -U rrr"]
interval: 30s
timeout: 30s
retries: 3
interval: 5s
timeout: 5s
retries: 20
lemmy:
build:
context: .
@ -22,6 +22,7 @@ services:
environment:
LEMMY_FRONT_END_DIR: /app/dist
DATABASE_URL: postgres://rrr:rrr@db:5432/rrr
restart: always
depends_on:
db:
condition: service_healthy

1
server/Cargo.lock generated
View File

@ -1359,6 +1359,7 @@ dependencies = [
"env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonwebtoken 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -24,4 +24,5 @@ rand = "0.6.5"
strum = "0.14.0"
strum_macros = "0.14.0"
jsonwebtoken = "*"
regex = "1"
regex = "*"
lazy_static = "*"

View File

@ -2,6 +2,7 @@ extern crate diesel;
use diesel::*;
use diesel::result::Error;
use serde::{Deserialize, Serialize};
use {SortType};
table! {
community_view (id) {
@ -83,17 +84,27 @@ impl CommunityView {
query.first::<Self>(conn)
}
pub fn list_all(conn: &PgConnection, from_user_id: Option<i32>) -> Result<Vec<Self>, Error> {
pub fn list(conn: &PgConnection, from_user_id: Option<i32>, sort: SortType, limit: Option<i64>) -> Result<Vec<Self>, Error> {
use actions::community_view::community_view::dsl::*;
let mut query = community_view.into_boxed();
// The view lets you pass a null user_id, if you're not logged in
if let Some(from_user_id) = from_user_id {
query = query.filter(user_id.eq(from_user_id))
.order_by((subscribed.desc(), number_of_subscribers.desc()));
} else {
query = query.filter(user_id.is_null())
.order_by(number_of_subscribers.desc());
match sort {
SortType::New => query = query.order_by(published.desc()).filter(user_id.is_null()),
SortType::TopAll => {
match from_user_id {
Some(from_user_id) => query = query.filter(user_id.eq(from_user_id)).order_by((subscribed.desc(), number_of_subscribers.desc())),
None => query = query.order_by(number_of_subscribers.desc()).filter(user_id.is_null())
}
}
_ => ()
};
if let Some(limit) = limit {
query = query.limit(limit);
};
query.load::<Self>(conn)

View File

@ -12,7 +12,7 @@ pub extern crate jsonwebtoken;
pub extern crate bcrypt;
pub extern crate regex;
#[macro_use] pub extern crate strum_macros;
#[macro_use] pub extern crate lazy_static;
pub mod schema;
pub mod apub;
pub mod actions;
@ -89,21 +89,42 @@ pub fn naive_now() -> NaiveDateTime {
}
pub fn is_email_regex(test: &str) -> bool {
let re = Regex::new(r"^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
re.is_match(test)
EMAIL_REGEX.is_match(test)
}
pub fn remove_slurs(test: &str) -> String {
SLUR_REGEX.replace_all(test, "*removed*").to_string()
}
pub fn has_slurs(test: &str) -> bool {
SLUR_REGEX.is_match(test)
}
#[cfg(test)]
mod tests {
use {Settings, is_email_regex};
use {Settings, is_email_regex, remove_slurs, has_slurs};
#[test]
fn test_api() {
assert_eq!(Settings::get().api_endpoint(), "http://0.0.0.0/api/v1");
}
#[test]
fn test_email() {
#[test] fn test_email() {
assert!(is_email_regex("gush@gmail.com"));
assert!(!is_email_regex("nada_neutho"));
}
#[test] fn test_slur_filter() {
let test = "coons test dindu ladyboy tranny. This is a bunch of other safe text.".to_string();
let slur_free = "No slurs here";
assert_eq!(remove_slurs(&test), "*removed* test *removed* *removed* *removed*. This is a bunch of other safe text.".to_string());
assert!(has_slurs(&test));
assert!(!has_slurs(slur_free));
}
}
lazy_static! {
static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
static ref SLUR_REGEX: Regex = Regex::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|\bnig(\b|g?(a|er)?s?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?))").unwrap();
}

View File

@ -10,7 +10,7 @@ use serde_json::{Value};
use bcrypt::{verify};
use std::str::FromStr;
use {Crud, Joinable, Likeable, Followable, establish_connection, naive_now, SortType};
use {Crud, Joinable, Likeable, Followable, establish_connection, naive_now, SortType, has_slurs, remove_slurs};
use actions::community::*;
use actions::user::*;
use actions::post::*;
@ -111,6 +111,8 @@ pub struct CommunityResponse {
#[derive(Serialize, Deserialize)]
pub struct ListCommunities {
sort: String,
limit: Option<i64>,
auth: Option<String>
}
@ -391,25 +393,25 @@ impl Handler<StandardMessage> for ChatServer {
let json: Value = serde_json::from_str(&msg.msg)
.expect("Couldn't parse message");
let data: &Value = &json["data"];
let data = &json["data"].to_string();
let op = &json["op"].as_str().unwrap();
let user_operation: UserOperation = UserOperation::from_str(&op).unwrap();
let res: String = match user_operation {
UserOperation::Login => {
let login: Login = serde_json::from_str(&data.to_string()).unwrap();
let login: Login = serde_json::from_str(data).unwrap();
login.perform(self, msg.id)
},
UserOperation::Register => {
let register: Register = serde_json::from_str(&data.to_string()).unwrap();
let register: Register = serde_json::from_str(data).unwrap();
register.perform(self, msg.id)
},
UserOperation::CreateCommunity => {
let create_community: CreateCommunity = serde_json::from_str(&data.to_string()).unwrap();
let create_community: CreateCommunity = serde_json::from_str(data).unwrap();
create_community.perform(self, msg.id)
},
UserOperation::ListCommunities => {
let list_communities: ListCommunities = serde_json::from_str(&data.to_string()).unwrap();
let list_communities: ListCommunities = serde_json::from_str(data).unwrap();
list_communities.perform(self, msg.id)
},
UserOperation::ListCategories => {
@ -417,55 +419,55 @@ impl Handler<StandardMessage> for ChatServer {
list_categories.perform(self, msg.id)
},
UserOperation::CreatePost => {
let create_post: CreatePost = serde_json::from_str(&data.to_string()).unwrap();
let create_post: CreatePost = serde_json::from_str(data).unwrap();
create_post.perform(self, msg.id)
},
UserOperation::GetPost => {
let get_post: GetPost = serde_json::from_str(&data.to_string()).unwrap();
let get_post: GetPost = serde_json::from_str(data).unwrap();
get_post.perform(self, msg.id)
},
UserOperation::GetCommunity => {
let get_community: GetCommunity = serde_json::from_str(&data.to_string()).unwrap();
let get_community: GetCommunity = serde_json::from_str(data).unwrap();
get_community.perform(self, msg.id)
},
UserOperation::CreateComment => {
let create_comment: CreateComment = serde_json::from_str(&data.to_string()).unwrap();
let create_comment: CreateComment = serde_json::from_str(data).unwrap();
create_comment.perform(self, msg.id)
},
UserOperation::EditComment => {
let edit_comment: EditComment = serde_json::from_str(&data.to_string()).unwrap();
let edit_comment: EditComment = serde_json::from_str(data).unwrap();
edit_comment.perform(self, msg.id)
},
UserOperation::CreateCommentLike => {
let create_comment_like: CreateCommentLike = serde_json::from_str(&data.to_string()).unwrap();
let create_comment_like: CreateCommentLike = serde_json::from_str(data).unwrap();
create_comment_like.perform(self, msg.id)
},
UserOperation::GetPosts => {
let get_posts: GetPosts = serde_json::from_str(&data.to_string()).unwrap();
let get_posts: GetPosts = serde_json::from_str(data).unwrap();
get_posts.perform(self, msg.id)
},
UserOperation::CreatePostLike => {
let create_post_like: CreatePostLike = serde_json::from_str(&data.to_string()).unwrap();
let create_post_like: CreatePostLike = serde_json::from_str(data).unwrap();
create_post_like.perform(self, msg.id)
},
UserOperation::EditPost => {
let edit_post: EditPost = serde_json::from_str(&data.to_string()).unwrap();
let edit_post: EditPost = serde_json::from_str(data).unwrap();
edit_post.perform(self, msg.id)
},
UserOperation::EditCommunity => {
let edit_community: EditCommunity = serde_json::from_str(&data.to_string()).unwrap();
let edit_community: EditCommunity = serde_json::from_str(data).unwrap();
edit_community.perform(self, msg.id)
},
UserOperation::FollowCommunity => {
let follow_community: FollowCommunity = serde_json::from_str(&data.to_string()).unwrap();
let follow_community: FollowCommunity = serde_json::from_str(data).unwrap();
follow_community.perform(self, msg.id)
},
UserOperation::GetFollowedCommunities => {
let followed_communities: GetFollowedCommunities = serde_json::from_str(&data.to_string()).unwrap();
let followed_communities: GetFollowedCommunities = serde_json::from_str(data).unwrap();
followed_communities.perform(self, msg.id)
},
UserOperation::GetUserDetails => {
let get_user_details: GetUserDetails = serde_json::from_str(&data.to_string()).unwrap();
let get_user_details: GetUserDetails = serde_json::from_str(data).unwrap();
get_user_details.perform(self, msg.id)
},
// _ => {
@ -541,6 +543,10 @@ impl Perform for Register {
return self.error("Passwords do not match.");
}
if has_slurs(&self.username) {
return self.error("No slurs");
}
// Register the new user
let user_form = UserForm {
name: self.username.to_owned(),
@ -587,6 +593,12 @@ impl Perform for CreateCommunity {
}
};
if has_slurs(&self.name) ||
has_slurs(&self.title) ||
(self.description.is_some() && has_slurs(&self.description.to_owned().unwrap())) {
return self.error("No slurs");
}
let user_id = claims.id;
// When you create a community, make sure the user becomes a moderator and a follower
@ -665,7 +677,9 @@ impl Perform for ListCommunities {
None => None
};
let communities: Vec<CommunityView> = CommunityView::list_all(&conn, user_id).unwrap();
let sort = SortType::from_str(&self.sort).expect("listing sort");
let communities: Vec<CommunityView> = CommunityView::list(&conn, user_id, sort, self.limit).unwrap();
// Return the jwt
serde_json::to_string(
@ -716,6 +730,11 @@ impl Perform for CreatePost {
}
};
if has_slurs(&self.name) ||
(self.body.is_some() && has_slurs(&self.body.to_owned().unwrap())) {
return self.error("No slurs");
}
let user_id = claims.id;
let post_form = PostForm {
@ -894,8 +913,10 @@ impl Perform for CreateComment {
let user_id = claims.id;
let content_slurs_removed = remove_slurs(&self.content.to_owned());
let comment_form = CommentForm {
content: self.content.to_owned(),
content: content_slurs_removed,
parent_id: self.parent_id.to_owned(),
post_id: self.post_id,
creator_id: user_id,
@ -976,8 +997,10 @@ impl Perform for EditComment {
return self.error("Incorrect creator.");
}
let content_slurs_removed = remove_slurs(&self.content.to_owned());
let comment_form = CommentForm {
content: self.content.to_owned(),
content: content_slurs_removed,
parent_id: self.parent_id,
post_id: self.post_id,
creator_id: user_id,
@ -1197,6 +1220,11 @@ impl Perform for EditPost {
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
if has_slurs(&self.name) ||
(self.body.is_some() && has_slurs(&self.body.to_owned().unwrap())) {
return self.error("No slurs");
}
let conn = establish_connection();
let claims = match Claims::decode(&self.auth) {
@ -1264,6 +1292,10 @@ impl Perform for EditCommunity {
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
if has_slurs(&self.name) || has_slurs(&self.title) {
return self.error("No slurs");
}
let conn = establish_connection();
let claims = match Claims::decode(&self.auth) {

View File

@ -11,7 +11,7 @@ const transformInferno = require('ts-transform-inferno').default;
const transformClasscat = require('ts-transform-classcat').default;
let fuse, app;
let isProduction = false;
var setVersion = require('./set_version.js').setVersion;
// var setVersion = require('./set_version.js').setVersion;
Sparky.task('config', _ => {
fuse = new FuseBox({
@ -42,7 +42,7 @@ Sparky.task('config', _ => {
});
app = fuse.bundle('app').instructions('>index.tsx');
});
Sparky.task('version', _ => setVersion());
// Sparky.task('version', _ => setVersion());
Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/'));
Sparky.task('env', _ => (isProduction = true));
Sparky.task('copy-assets', () => Sparky.src('assets/*.svg').dest('dist/'));

2
ui/set_version.js Normal file → Executable file
View File

@ -7,3 +7,5 @@ exports.setVersion = function() {
let line = `export let version: string = "${revision}";`;
fs.writeFileSync("./src/version.ts", line);
}
this.setVersion()

View File

@ -2,7 +2,7 @@ import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router';
import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, Community, ListCommunitiesResponse, CommunityResponse, FollowCommunityForm } from '../interfaces';
import { UserOperation, Community, ListCommunitiesResponse, CommunityResponse, FollowCommunityForm, ListCommunitiesForm, SortType } from '../interfaces';
import { WebSocketService } from '../services';
import { msgOp } from '../utils';
@ -30,7 +30,12 @@ export class Communities extends Component<any, CommunitiesState> {
(err) => console.error(err),
() => console.log('complete')
);
WebSocketService.Instance.listCommunities();
let listCommunitiesForm: ListCommunitiesForm = {
sort: SortType[SortType.TopAll]
}
WebSocketService.Instance.listCommunities(listCommunitiesForm);
}
@ -45,7 +50,7 @@ export class Communities extends Component<any, CommunitiesState> {
render() {
return (
<div class="container-fluid">
<div class="container">
{this.state.loading ?
<h4 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
<div>

View File

@ -155,6 +155,7 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
if (msg.error) {
alert(msg.error);
this.state.loading = false;
this.setState(this.state);
return;
} else if (op == UserOperation.ListCategories){
let res: ListCategoriesResponse = msg;

View File

@ -97,13 +97,13 @@ export class Login extends Component<any, State> {
<div class="form-group row">
<label class="col-sm-2 col-form-label">Username</label>
<div class="col-sm-10">
<input type="text" class="form-control" value={this.state.registerForm.username} onInput={linkEvent(this, this.handleRegisterUsernameChange)} required minLength={3} />
<input type="text" class="form-control" value={this.state.registerForm.username} onInput={linkEvent(this, this.handleRegisterUsernameChange)} required minLength={3} pattern="[a-zA-Z0-9_]+" />
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" value={this.state.registerForm.email} onInput={linkEvent(this, this.handleRegisterEmailChange)} minLength={3} />
<input type="email" class="form-control" placeholder="Optional" value={this.state.registerForm.email} onInput={linkEvent(this, this.handleRegisterEmailChange)} minLength={3} />
</div>
</div>
<div class="form-group row">

View File

@ -2,13 +2,14 @@ import { Component } from 'inferno';
import { Link } from 'inferno-router';
import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse } from '../interfaces';
import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse, ListCommunitiesForm, ListCommunitiesResponse, Community, SortType } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { PostListings } from './post-listings';
import { msgOp } from '../utils';
import { msgOp, repoUrl } from '../utils';
interface State {
subscribedCommunities: Array<CommunityUser>;
trendingCommunities: Array<Community>;
loading: boolean;
}
@ -17,6 +18,7 @@ export class Main extends Component<any, State> {
private subscription: Subscription;
private emptyState: State = {
subscribedCommunities: [],
trendingCommunities: [],
loading: true
}
@ -36,6 +38,13 @@ export class Main extends Component<any, State> {
if (UserService.Instance.loggedIn) {
WebSocketService.Instance.getFollowedCommunities();
}
let listCommunitiesForm: ListCommunitiesForm = {
sort: SortType[SortType.New],
limit: 8
}
WebSocketService.Instance.listCommunities(listCommunitiesForm);
}
componentWillUnmount() {
@ -46,26 +55,26 @@ export class Main extends Component<any, State> {
return (
<div class="container">
<div class="row">
<div class="col-12 col-md-9">
<div class="col-12 col-md-8">
<PostListings />
</div>
<div class="col-12 col-md-3">
<h4>A Landing message</h4>
{UserService.Instance.loggedIn &&
<div class="col-12 col-md-4">
{this.state.loading ?
<h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
<div>
{this.trendingCommunities()}
{UserService.Instance.loggedIn ?
<div>
{this.state.loading ?
<h4 class="mt-3"><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
<div>
<hr />
<h4>Subscribed forums</h4>
<ul class="list-unstyled">
{this.state.subscribedCommunities.map(community =>
<li><Link to={`/community/${community.community_id}`}>{community.community_name}</Link></li>
)}
</ul>
</div>
}
</div>
<h4>Subscribed forums</h4>
<ul class="list-inline">
{this.state.subscribedCommunities.map(community =>
<li class="list-inline-item"><Link to={`/community/${community.community_id}`}>{community.community_name}</Link></li>
)}
</ul>
</div> :
this.landing()
}
</div>
}
</div>
</div>
@ -73,6 +82,34 @@ export class Main extends Component<any, State> {
)
}
trendingCommunities() {
return (
<div>
<h4>Trending forums</h4>
<ul class="list-inline">
{this.state.trendingCommunities.map(community =>
<li class="list-inline-item"><Link to={`/community/${community.id}`}>{community.name}</Link></li>
)}
</ul>
</div>
)
}
landing() {
return (
<div>
<h4>Welcome to
<svg class="icon mx-2"><use xlinkHref="#icon-mouse"></use></svg>
<a href={repoUrl}>Lemmy<sup>Beta</sup></a>
</h4>
<p>Lemmy is a <a href="https://en.wikipedia.org/wiki/Link_aggregation">link aggregator</a> / reddit alternative, intended to work in the <a href="https://en.wikipedia.org/wiki/Fediverse">fediverse</a>.</p>
<p>Its self-hostable, has live-updating comment threads, and is tiny (<code>~80kB</code>). Federation into the ActivityPub network is on the roadmap.</p>
<p>This is a <b>very early beta version</b>, and a lot of features are currently broken or missing.</p>
<p>Suggest new features or report bugs <a href={repoUrl}>here.</a></p>
<p>Made with <a href="https://www.rust-lang.org">Rust</a>, <a href="https://actix.rs/">Actix</a>, <a href="https://www.infernojs.org">Inferno</a>, <a href="https://www.typescriptlang.org/">Typescript</a>.</p>
</div>
)
}
parseMessage(msg: any) {
console.log(msg);
@ -85,6 +122,11 @@ export class Main extends Component<any, State> {
this.state.subscribedCommunities = res.communities;
this.state.loading = false;
this.setState(this.state);
} else if (op == UserOperation.ListCommunities) {
let res: ListCommunitiesResponse = msg;
this.state.trendingCommunities = res.communities;
this.state.loading = false;
this.setState(this.state);
}
}
}

View File

@ -25,7 +25,7 @@ export class Navbar extends Component<any, NavbarState> {
// Subscribe to user changes
UserService.Instance.sub.subscribe(user => {
let loggedIn: boolean = user !== null;
let loggedIn: boolean = user !== undefined;
this.setState({isLoggedIn: loggedIn});
});
}
@ -40,7 +40,7 @@ export class Navbar extends Component<any, NavbarState> {
// TODO toggle css collapse
navbar() {
return (
<nav class="navbar navbar-expand-sm navbar-light bg-light p-0 px-3 shadow">
<nav class="container navbar navbar-expand-md navbar-light navbar-bg p-0 px-3">
<a title={version} class="navbar-brand" href="#">
<svg class="icon mr-2"><use xlinkHref="#icon-mouse"></use></svg>
Lemmy
@ -74,7 +74,7 @@ export class Navbar extends Component<any, NavbarState> {
<a role="button" class="dropdown-item pointer" onClick={ linkEvent(this, this.handleLogoutClick) }>Logout</a>
</div>
</li> :
<Link class="nav-link" to="/login">Login</Link>
<Link class="nav-link" to="/login">Login / Sign up</Link>
}
</ul>
</div>

View File

@ -1,7 +1,7 @@
import { Component, linkEvent } from 'inferno';
import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators';
import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse } from '../interfaces';
import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse, ListCommunitiesForm, SortType } from '../interfaces';
import { WebSocketService } from '../services';
import { msgOp } from '../utils';
import * as autosize from 'autosize';
@ -56,7 +56,11 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
() => console.log('complete')
);
WebSocketService.Instance.listCommunities();
let listCommunitiesForm: ListCommunitiesForm = {
sort: SortType[SortType.TopAll]
}
WebSocketService.Instance.listCommunities(listCommunitiesForm);
}
componentDidMount() {
@ -151,7 +155,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
parseMessage(msg: any) {
let op: UserOperation = msgOp(msg);
if (msg.error) {
alert(msg.error);
this.state.loading = false;
this.setState(this.state);
return;
} else if (op == UserOperation.ListCommunities) {
let res: ListCommunitiesResponse = msg;

View File

@ -101,17 +101,17 @@ export class Post extends Component<any, PostState> {
sortRadios() {
return (
<div class="btn-group btn-group-toggle mb-3">
<label className={`btn btn-sm btn-secondary ${this.state.commentSort === CommentSortType.Hot && 'active'}`}>Hot
<label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.Hot && 'active'}`}>Hot
<input type="radio" value={CommentSortType.Hot}
checked={this.state.commentSort === CommentSortType.Hot}
onChange={linkEvent(this, this.handleCommentSortChange)} />
</label>
<label className={`btn btn-sm btn-secondary ${this.state.commentSort === CommentSortType.Top && 'active'}`}>Top
<label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.Top && 'active'}`}>Top
<input type="radio" value={CommentSortType.Top}
checked={this.state.commentSort === CommentSortType.Top}
onChange={linkEvent(this, this.handleCommentSortChange)} />
</label>
<label className={`btn btn-sm btn-secondary ${this.state.commentSort === CommentSortType.New && 'active'}`}>New
<label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.New && 'active'}`}>New
<input type="radio" value={CommentSortType.New}
checked={this.state.commentSort === CommentSortType.New}
onChange={linkEvent(this, this.handleCommentSortChange)} />

View File

@ -56,7 +56,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
}
</ul>
}
<ul class="list-inline">
<ul class="mt-1 list-inline">
<li className="list-inline-item"><Link className="badge badge-light" to="/communities">{community.category_name}</Link></li>
<li className="list-inline-item badge badge-light">{community.number_of_subscribers} Subscribers</li>
<li className="list-inline-item badge badge-light">{community.number_of_posts} Posts</li>

View File

@ -67,6 +67,12 @@ export interface CommunityResponse {
community: Community;
}
export interface ListCommunitiesForm {
sort: string;
limit?: number;
auth?: string;
}
export interface ListCommunitiesResponse {
op: string;
communities: Array<Community>;

View File

@ -66,3 +66,12 @@ body {
0% { transform: rotate(0deg); }
100% { transform: rotate(359deg); }
}
.dropdown-menu {
z-index: 2000;
}
.navbar-bg {
background-color: #222;
}

View File

@ -25,10 +25,10 @@ export class UserService {
}
public logout() {
this.user = null;
this.user = undefined;
Cookies.remove("jwt");
console.log("Logged out.");
this.sub.next(null);
this.sub.next(undefined);
}
public get loggedIn(): boolean {

View File

@ -1,5 +1,5 @@
import { wsUri } from '../env';
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm } from '../interfaces';
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm } from '../interfaces';
import { webSocket } from 'rxjs/webSocket';
import { Subject } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
@ -47,9 +47,9 @@ export class WebSocketService {
this.subject.next(this.wsSendWrapper(UserOperation.FollowCommunity, followCommunityForm));
}
public listCommunities() {
let data = {auth: UserService.Instance.auth };
this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, data));
public listCommunities(form: ListCommunitiesForm) {
this.setAuth(form, false);
this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, form));
}
public getFollowedCommunities() {

View File

@ -1 +1 @@
export let version: string = "v0.0.2-0-gdae6651";
export let version: string = "v0.0.2-9-g8e5a5d1";