Merge branch 'dev'

This commit is contained in:
Dessalines 2019-10-20 22:26:38 -07:00
commit 088f47c611
29 changed files with 379 additions and 262 deletions

16
README.md vendored
View file

@ -202,14 +202,14 @@ If you'd like to add translations, take a look a look at the [English translatio
lang | done | missing lang | done | missing
--- | --- | --- --- | --- | ---
de | 82% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,subscribed,expires,recent_comments,nsfw,show_nsfw,theme,crypto,monero,joined,by,to,transfer_community,transfer_site,are_you_sure,yes,no de | 81% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,subscribed,replies,mentions,expires,recent_comments,nsfw,show_nsfw,theme,crypto,monero,joined,by,to,transfer_community,transfer_site,are_you_sure,yes,no
eo | 91% | number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,stickied,delete_account,delete_account_confirm,banned,creator,number_online,theme,are_you_sure,yes,no eo | 90% | number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,theme,are_you_sure,yes,no
es | 100% | es | 99% | replies,mentions
fr | 100% | fr | 99% | replies,mentions
nl | 93% | preview,upload_image,formatting_help,view_source,sticky,unsticky,stickied,delete_account,delete_account_confirm,banned,creator,number_online,theme nl | 92% | preview,upload_image,formatting_help,view_source,sticky,unsticky,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,theme
ru | 86% | cross_posts,cross_post,number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,stickied,delete_account,delete_account_confirm,banned,creator,number_online,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no ru | 86% | cross_posts,cross_post,number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no
sv | 100% | sv | 99% | replies,mentions
zh | 84% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no zh | 83% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no
If you'd like to update this report, run: If you'd like to update this report, run:

View file

@ -0,0 +1,2 @@
alter table user_ drop column default_sort_type;
alter table user_ drop column default_listing_type;

View file

@ -0,0 +1,2 @@
alter table user_ add column default_sort_type smallint default 0 not null;
alter table user_ add column default_listing_type smallint default 1 not null;

View file

@ -235,7 +235,7 @@ impl Perform<GetPostsResponse> for Oper<GetPosts> {
None => false, None => false,
}; };
let type_ = PostListingType::from_str(&data.type_)?; let type_ = ListingType::from_str(&data.type_)?;
let sort = SortType::from_str(&data.sort)?; let sort = SortType::from_str(&data.sort)?;
let posts = match PostView::list( let posts = match PostView::list(

View file

@ -321,7 +321,7 @@ impl Perform<SearchResponse> for Oper<Search> {
SearchType::Posts => { SearchType::Posts => {
posts = PostView::list( posts = PostView::list(
&conn, &conn,
PostListingType::All, ListingType::All,
&sort, &sort,
data.community_id, data.community_id,
None, None,
@ -365,7 +365,7 @@ impl Perform<SearchResponse> for Oper<Search> {
SearchType::All => { SearchType::All => {
posts = PostView::list( posts = PostView::list(
&conn, &conn,
PostListingType::All, ListingType::All,
&sort, &sort,
data.community_id, data.community_id,
None, None,
@ -403,7 +403,7 @@ impl Perform<SearchResponse> for Oper<Search> {
SearchType::Url => { SearchType::Url => {
posts = PostView::list( posts = PostView::list(
&conn, &conn,
PostListingType::All, ListingType::All,
&sort, &sort,
data.community_id, data.community_id,
None, None,

View file

@ -22,6 +22,8 @@ pub struct Register {
pub struct SaveUserSettings { pub struct SaveUserSettings {
show_nsfw: bool, show_nsfw: bool,
theme: String, theme: String,
default_sort_type: i16,
default_listing_type: i16,
auth: String, auth: String,
} }
@ -198,6 +200,8 @@ impl Perform<LoginResponse> for Oper<Register> {
banned: false, banned: false,
show_nsfw: data.show_nsfw, show_nsfw: data.show_nsfw,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
// Create the user // Create the user
@ -289,6 +293,8 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
banned: read_user.banned, banned: read_user.banned,
show_nsfw: data.show_nsfw, show_nsfw: data.show_nsfw,
theme: data.theme.to_owned(), theme: data.theme.to_owned(),
default_sort_type: data.default_sort_type,
default_listing_type: data.default_listing_type,
}; };
let updated_user = match User_::update(&conn, user_id, &user_form) { let updated_user = match User_::update(&conn, user_id, &user_form) {
@ -346,7 +352,7 @@ impl Perform<GetUserDetailsResponse> for Oper<GetUserDetails> {
let posts = if data.saved_only { let posts = if data.saved_only {
PostView::list( PostView::list(
&conn, &conn,
PostListingType::All, ListingType::All,
&sort, &sort,
data.community_id, data.community_id,
None, None,
@ -362,7 +368,7 @@ impl Perform<GetUserDetailsResponse> for Oper<GetUserDetails> {
} else { } else {
PostView::list( PostView::list(
&conn, &conn,
PostListingType::All, ListingType::All,
&sort, &sort,
data.community_id, data.community_id,
Some(user_details_id), Some(user_details_id),
@ -453,6 +459,8 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
banned: read_user.banned, banned: read_user.banned,
show_nsfw: read_user.show_nsfw, show_nsfw: read_user.show_nsfw,
theme: read_user.theme, theme: read_user.theme,
default_sort_type: read_user.default_sort_type,
default_listing_type: read_user.default_listing_type,
}; };
match User_::update(&conn, data.user_id, &user_form) { match User_::update(&conn, data.user_id, &user_form) {
@ -512,6 +520,8 @@ impl Perform<BanUserResponse> for Oper<BanUser> {
banned: data.ban, banned: data.ban,
show_nsfw: read_user.show_nsfw, show_nsfw: read_user.show_nsfw,
theme: read_user.theme, theme: read_user.theme,
default_sort_type: read_user.default_sort_type,
default_listing_type: read_user.default_listing_type,
}; };
match User_::update(&conn, data.user_id, &user_form) { match User_::update(&conn, data.user_id, &user_form) {
@ -751,7 +761,7 @@ impl Perform<LoginResponse> for Oper<DeleteAccount> {
// Posts // Posts
let posts = PostView::list( let posts = PostView::list(
&conn, &conn,
PostListingType::All, ListingType::All,
&SortType::New, &SortType::New,
None, None,
Some(user_id), Some(user_id),

View file

@ -55,6 +55,7 @@ impl User_ {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::User_; use super::User_;
use crate::db::{ListingType, SortType};
use crate::naive_now; use crate::naive_now;
#[test] #[test]
@ -73,6 +74,8 @@ mod tests {
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let person = expected_user.person(); let person = expected_user.person();

View file

@ -179,6 +179,8 @@ mod tests {
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();

View file

@ -264,6 +264,8 @@ mod tests {
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();

View file

@ -265,6 +265,8 @@ mod tests {
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();

View file

@ -106,6 +106,13 @@ pub enum SortType {
TopAll, TopAll,
} }
#[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
pub enum ListingType {
All,
Subscribed,
Community,
}
#[derive(EnumString, ToString, Debug, Serialize, Deserialize)] #[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
pub enum SearchType { pub enum SearchType {
All, All,

View file

@ -447,6 +447,8 @@ mod tests {
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let inserted_mod = User_::create(&conn, &new_mod).unwrap(); let inserted_mod = User_::create(&conn, &new_mod).unwrap();
@ -462,6 +464,8 @@ mod tests {
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();

View file

@ -192,6 +192,8 @@ mod tests {
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();

View file

@ -1,12 +1,5 @@
use super::*; use super::*;
#[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
pub enum PostListingType {
All,
Subscribed,
Community,
}
// The faked schema since diesel doesn't do views // The faked schema since diesel doesn't do views
table! { table! {
post_view (id) { post_view (id) {
@ -83,7 +76,7 @@ pub struct PostView {
impl PostView { impl PostView {
pub fn list( pub fn list(
conn: &PgConnection, conn: &PgConnection,
type_: PostListingType, type_: ListingType,
sort: &SortType, sort: &SortType,
for_community_id: Option<i32>, for_community_id: Option<i32>,
for_creator_id: Option<i32>, for_creator_id: Option<i32>,
@ -129,7 +122,7 @@ impl PostView {
}; };
match type_ { match type_ {
PostListingType::Subscribed => { ListingType::Subscribed => {
query = query.filter(subscribed.eq(true)); query = query.filter(subscribed.eq(true));
} }
_ => {} _ => {}
@ -226,6 +219,8 @@ mod tests {
banned: false, banned: false,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
@ -351,7 +346,7 @@ mod tests {
let read_post_listings_with_user = PostView::list( let read_post_listings_with_user = PostView::list(
&conn, &conn,
PostListingType::Community, ListingType::Community,
&SortType::New, &SortType::New,
Some(inserted_community.id), Some(inserted_community.id),
None, None,
@ -367,7 +362,7 @@ mod tests {
.unwrap(); .unwrap();
let read_post_listings_no_user = PostView::list( let read_post_listings_no_user = PostView::list(
&conn, &conn,
PostListingType::Community, ListingType::Community,
&SortType::New, &SortType::New,
Some(inserted_community.id), Some(inserted_community.id),
None, None,

View file

@ -21,6 +21,8 @@ pub struct User_ {
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub show_nsfw: bool, pub show_nsfw: bool,
pub theme: String, pub theme: String,
pub default_sort_type: i16,
pub default_listing_type: i16,
} }
#[derive(Insertable, AsChangeset, Clone)] #[derive(Insertable, AsChangeset, Clone)]
@ -36,6 +38,8 @@ pub struct UserForm {
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub show_nsfw: bool, pub show_nsfw: bool,
pub theme: String, pub theme: String,
pub default_sort_type: i16,
pub default_listing_type: i16,
} }
impl Crud<UserForm> for User_ { impl Crud<UserForm> for User_ {
@ -77,6 +81,8 @@ pub struct Claims {
pub iss: String, pub iss: String,
pub show_nsfw: bool, pub show_nsfw: bool,
pub theme: String, pub theme: String,
pub default_sort_type: i16,
pub default_listing_type: i16,
} }
impl Claims { impl Claims {
@ -98,6 +104,8 @@ impl User_ {
iss: self.fedi_name.to_owned(), iss: self.fedi_name.to_owned(),
show_nsfw: self.show_nsfw, show_nsfw: self.show_nsfw,
theme: self.theme.to_owned(), theme: self.theme.to_owned(),
default_sort_type: self.default_sort_type,
default_listing_type: self.default_listing_type,
}; };
encode( encode(
&Header::default(), &Header::default(),
@ -146,6 +154,8 @@ mod tests {
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
@ -164,6 +174,8 @@ mod tests {
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let read_user = User_::read(&conn, inserted_user.id).unwrap(); let read_user = User_::read(&conn, inserted_user.id).unwrap();

View file

@ -73,6 +73,8 @@ mod tests {
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
@ -88,6 +90,8 @@ mod tests {
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(), theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
}; };
let inserted_recipient = User_::create(&conn, &recipient_form).unwrap(); let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();

View file

@ -255,6 +255,8 @@ table! {
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
show_nsfw -> Bool, show_nsfw -> Bool,
theme -> Varchar, theme -> Varchar,
default_sort_type -> Int2,
default_listing_type -> Int2,
} }
} }

View file

@ -136,7 +136,7 @@ impl ChatServer {
let conn = establish_connection(); let conn = establish_connection();
let posts = PostView::list( let posts = PostView::list(
&conn, &conn,
PostListingType::Community, ListingType::Community,
&SortType::New, &SortType::New,
Some(*community_id), Some(*community_id),
None, None,

19
ui/package.json vendored
View file

@ -22,6 +22,7 @@
"classcat": "^1.1.3", "classcat": "^1.1.3",
"dotenv": "^6.1.0", "dotenv": "^6.1.0",
"emoji-short-name": "^0.1.0", "emoji-short-name": "^0.1.0",
"husky": "^3.0.9",
"i18next": "^17.0.9", "i18next": "^17.0.9",
"inferno": "^7.0.1", "inferno": "^7.0.1",
"inferno-i18next": "nimbusec-oss/inferno-i18next", "inferno-i18next": "nimbusec-oss/inferno-i18next",
@ -45,7 +46,6 @@
"eslint-plugin-inferno": "^7.14.3", "eslint-plugin-inferno": "^7.14.3",
"eslint-plugin-jane": "^7.0.0", "eslint-plugin-jane": "^7.0.0",
"fuse-box": "^3.1.3", "fuse-box": "^3.1.3",
"husky": "^3.0.9",
"lint-staged": "^9.4.2", "lint-staged": "^9.4.2",
"sortpack": "^2.0.1", "sortpack": "^2.0.1",
"ts-transform-classcat": "^0.0.2", "ts-transform-classcat": "^0.0.2",
@ -58,24 +58,13 @@
"engineStrict": true, "engineStrict": true,
"husky": { "husky": {
"hooks": { "hooks": {
"pre-commit": "lint-staged", "pre-commit": "lint-staged"
"pre-push": "yarn run lint"
} }
}, },
"lint-staged": { "lint-staged": {
"*.js": [ "*.{ts,tsx,js}": [
"prettier --write", "prettier --write",
"yarn run lint", "eslint --fix",
"git add"
],
"*.ts": [
"prettier --write",
"yarn run lint",
"git add"
],
"*.tsx": [
"prettier --write",
"yarn run lint",
"git add" "git add"
], ],
"package.json": [ "package.json": [

View file

@ -15,8 +15,9 @@ import {
GetPostsResponse, GetPostsResponse,
CreatePostLikeResponse, CreatePostLikeResponse,
} from '../interfaces'; } from '../interfaces';
import { WebSocketService } from '../services'; import { WebSocketService, UserService } from '../services';
import { PostListings } from './post-listings'; import { PostListings } from './post-listings';
import { SortSelect } from './sort-select';
import { Sidebar } from './sidebar'; import { Sidebar } from './sidebar';
import { import {
msgOp, msgOp,
@ -71,6 +72,8 @@ export class Community extends Component<any, State> {
getSortTypeFromProps(props: any): SortType { getSortTypeFromProps(props: any): SortType {
return props.match.params.sort return props.match.params.sort
? routeSortTypeToEnum(props.match.params.sort) ? routeSortTypeToEnum(props.match.params.sort)
: UserService.Instance.user
? UserService.Instance.user.default_sort_type
: SortType.Hot; : SortType.Hot;
} }
@ -82,6 +85,7 @@ export class Community extends Component<any, State> {
super(props, context); super(props, context);
this.state = this.emptyState; this.state = this.emptyState;
this.handleSortChange = this.handleSortChange.bind(this);
this.subscription = WebSocketService.Instance.subject this.subscription = WebSocketService.Instance.subject
.pipe( .pipe(
@ -112,10 +116,13 @@ export class Community extends Component<any, State> {
// Necessary for back button for some reason // Necessary for back button for some reason
componentWillReceiveProps(nextProps: any) { componentWillReceiveProps(nextProps: any) {
if (nextProps.history.action == 'POP') { if (
this.state = this.emptyState; nextProps.history.action == 'POP' ||
nextProps.history.action == 'PUSH'
) {
this.state.sort = this.getSortTypeFromProps(nextProps); this.state.sort = this.getSortTypeFromProps(nextProps);
this.state.page = this.getPageFromProps(nextProps); this.state.page = this.getPageFromProps(nextProps);
this.setState(this.state);
this.fetchPosts(); this.fetchPosts();
} }
} }
@ -164,38 +171,8 @@ export class Community extends Component<any, State> {
selects() { selects() {
return ( return (
<div className="mb-2"> <div class="mb-2">
<select <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
value={this.state.sort}
onChange={linkEvent(this, this.handleSortChange)}
class="custom-select custom-select-sm w-auto"
>
<option disabled>
<T i18nKey="sort_type">#</T>
</option>
<option value={SortType.Hot}>
<T i18nKey="hot">#</T>
</option>
<option value={SortType.New}>
<T i18nKey="new">#</T>
</option>
<option disabled></option>
<option value={SortType.TopDay}>
<T i18nKey="top_day">#</T>
</option>
<option value={SortType.TopWeek}>
<T i18nKey="week">#</T>
</option>
<option value={SortType.TopMonth}>
<T i18nKey="month">#</T>
</option>
<option value={SortType.TopYear}>
<T i18nKey="year">#</T>
</option>
<option value={SortType.TopAll}>
<T i18nKey="all">#</T>
</option>
</select>
</div> </div>
); );
} }
@ -237,12 +214,13 @@ export class Community extends Component<any, State> {
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
handleSortChange(i: Community, event: any) { handleSortChange(val: SortType) {
i.state.sort = Number(event.target.value); this.state.sort = val;
i.state.page = 1; this.state.page = 1;
i.setState(i.state); this.state.loading = true;
i.updateUrl(); this.setState(this.state);
i.fetchPosts(); this.updateUrl();
this.fetchPosts();
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }

View file

@ -16,6 +16,7 @@ import {
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { msgOp } from '../utils'; import { msgOp } from '../utils';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import { SortSelect } from './sort-select';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next'; import { T } from 'inferno-i18next';
@ -54,6 +55,7 @@ export class Inbox extends Component<any, InboxState> {
super(props, context); super(props, context);
this.state = this.emptyState; this.state = this.emptyState;
this.handleSortChange = this.handleSortChange.bind(this);
this.subscription = WebSocketService.Instance.subject this.subscription = WebSocketService.Instance.subject
.pipe( .pipe(
@ -153,33 +155,11 @@ export class Inbox extends Component<any, InboxState> {
<T i18nKey="mentions">#</T> <T i18nKey="mentions">#</T>
</option> </option>
</select> </select>
<select <SortSelect
value={this.state.sort} sort={this.state.sort}
onChange={linkEvent(this, this.handleSortChange)} onChange={this.handleSortChange}
class="custom-select custom-select-sm w-auto" hideHot
> />
<option disabled>
<T i18nKey="sort_type">#</T>
</option>
<option value={SortType.New}>
<T i18nKey="new">#</T>
</option>
<option value={SortType.TopDay}>
<T i18nKey="top_day">#</T>
</option>
<option value={SortType.TopWeek}>
<T i18nKey="week">#</T>
</option>
<option value={SortType.TopMonth}>
<T i18nKey="month">#</T>
</option>
<option value={SortType.TopYear}>
<T i18nKey="year">#</T>
</option>
<option value={SortType.TopAll}>
<T i18nKey="all">#</T>
</option>
</select>
</div> </div>
); );
} }
@ -300,11 +280,11 @@ export class Inbox extends Component<any, InboxState> {
WebSocketService.Instance.getUserMentions(userMentionsForm); WebSocketService.Instance.getUserMentions(userMentionsForm);
} }
handleSortChange(i: Inbox, event: any) { handleSortChange(val: SortType) {
i.state.sort = Number(event.target.value); this.state.sort = val;
i.state.page = 1; this.state.page = 1;
i.setState(i.state); this.setState(this.state);
i.refetch(); this.refetch();
} }
markAllAsRead() { markAllAsRead() {

View file

@ -0,0 +1,68 @@
import { Component, linkEvent } from 'inferno';
import { ListingType } from '../interfaces';
import { UserService } from '../services';
import { i18n } from '../i18next';
interface ListingTypeSelectProps {
type_: ListingType;
onChange?(val: ListingType): any;
}
interface ListingTypeSelectState {
type_: ListingType;
}
export class ListingTypeSelect extends Component<
ListingTypeSelectProps,
ListingTypeSelectState
> {
private emptyState: ListingTypeSelectState = {
type_: this.props.type_,
};
constructor(props: any, context: any) {
super(props, context);
this.state = this.emptyState;
}
render() {
return (
<div class="btn-group btn-group-toggle">
<label
className={`btn btn-sm btn-secondary
${this.state.type_ == ListingType.Subscribed && 'active'}
${UserService.Instance.user == undefined ? 'disabled' : 'pointer'}
`}
>
<input
type="radio"
value={ListingType.Subscribed}
checked={this.state.type_ == ListingType.Subscribed}
onChange={linkEvent(this, this.handleTypeChange)}
disabled={UserService.Instance.user == undefined}
/>
{i18n.t('subscribed')}
</label>
<label
className={`pointer btn btn-sm btn-secondary ${this.state.type_ ==
ListingType.All && 'active'}`}
>
<input
type="radio"
value={ListingType.All}
checked={this.state.type_ == ListingType.All}
onChange={linkEvent(this, this.handleTypeChange)}
/>
{i18n.t('all')}
</label>
</div>
);
}
handleTypeChange(i: ListingTypeSelect, event: any) {
i.state.type_ = Number(event.target.value);
i.setState(i.state);
i.props.onChange(i.state.type_);
}
}

View file

@ -20,6 +20,8 @@ import {
} from '../interfaces'; } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { PostListings } from './post-listings'; import { PostListings } from './post-listings';
import { SortSelect } from './sort-select';
import { ListingTypeSelect } from './listing-type-select';
import { SiteForm } from './site-form'; import { SiteForm } from './site-form';
import { import {
msgOp, msgOp,
@ -80,13 +82,15 @@ export class Main extends Component<any, MainState> {
return props.match.params.type return props.match.params.type
? routeListingTypeToEnum(props.match.params.type) ? routeListingTypeToEnum(props.match.params.type)
: UserService.Instance.user : UserService.Instance.user
? ListingType.Subscribed ? UserService.Instance.user.default_listing_type
: ListingType.All; : ListingType.All;
} }
getSortTypeFromProps(props: any): SortType { getSortTypeFromProps(props: any): SortType {
return props.match.params.sort return props.match.params.sort
? routeSortTypeToEnum(props.match.params.sort) ? routeSortTypeToEnum(props.match.params.sort)
: UserService.Instance.user
? UserService.Instance.user.default_sort_type
: SortType.Hot; : SortType.Hot;
} }
@ -99,6 +103,8 @@ export class Main extends Component<any, MainState> {
this.state = this.emptyState; this.state = this.emptyState;
this.handleEditCancel = this.handleEditCancel.bind(this); this.handleEditCancel = this.handleEditCancel.bind(this);
this.handleSortChange = this.handleSortChange.bind(this);
this.handleTypeChange = this.handleTypeChange.bind(this);
this.subscription = WebSocketService.Instance.subject this.subscription = WebSocketService.Instance.subject
.pipe( .pipe(
@ -421,66 +427,13 @@ export class Main extends Component<any, MainState> {
selects() { selects() {
return ( return (
<div className="mb-3"> <div className="mb-3">
<div class="btn-group btn-group-toggle"> <ListingTypeSelect
<label type_={this.state.type_}
className={`btn btn-sm btn-secondary onChange={this.handleTypeChange}
${this.state.type_ == ListingType.Subscribed && 'active'} />
${UserService.Instance.user == undefined ? 'disabled' : 'pointer'} <span class="ml-2">
`} <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
> </span>
<input
type="radio"
value={ListingType.Subscribed}
checked={this.state.type_ == ListingType.Subscribed}
onChange={linkEvent(this, this.handleTypeChange)}
disabled={UserService.Instance.user == undefined}
/>
{i18n.t('subscribed')}
</label>
<label
className={`pointer btn btn-sm btn-secondary ${this.state.type_ ==
ListingType.All && 'active'}`}
>
<input
type="radio"
value={ListingType.All}
checked={this.state.type_ == ListingType.All}
onChange={linkEvent(this, this.handleTypeChange)}
/>
{i18n.t('all')}
</label>
</div>
<select
value={this.state.sort}
onChange={linkEvent(this, this.handleSortChange)}
class="ml-2 custom-select custom-select-sm w-auto"
>
<option disabled>
<T i18nKey="sort_type">#</T>
</option>
<option value={SortType.Hot}>
<T i18nKey="hot">#</T>
</option>
<option value={SortType.New}>
<T i18nKey="new">#</T>
</option>
<option disabled></option>
<option value={SortType.TopDay}>
<T i18nKey="top_day">#</T>
</option>
<option value={SortType.TopWeek}>
<T i18nKey="week">#</T>
</option>
<option value={SortType.TopMonth}>
<T i18nKey="month">#</T>
</option>
<option value={SortType.TopYear}>
<T i18nKey="year">#</T>
</option>
<option value={SortType.TopAll}>
<T i18nKey="all">#</T>
</option>
</select>
</div> </div>
); );
} }
@ -543,23 +496,23 @@ export class Main extends Component<any, MainState> {
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
handleSortChange(i: Main, event: any) { handleSortChange(val: SortType) {
i.state.sort = Number(event.target.value); this.state.sort = val;
i.state.page = 1; this.state.page = 1;
i.state.loading = true; this.state.loading = true;
i.setState(i.state); this.setState(this.state);
i.updateUrl(); this.updateUrl();
i.fetchPosts(); this.fetchPosts();
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
handleTypeChange(i: Main, event: any) { handleTypeChange(val: ListingType) {
i.state.type_ = Number(event.target.value); this.state.type_ = val;
i.state.page = 1; this.state.page = 1;
i.state.loading = true; this.state.loading = true;
i.setState(i.state); this.setState(this.state);
i.updateUrl(); this.updateUrl();
i.fetchPosts(); this.fetchPosts();
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }

View file

@ -21,6 +21,7 @@ import {
routeSortTypeToEnum, routeSortTypeToEnum,
} from '../utils'; } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { SortSelect } from './sort-select';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next'; import { T } from 'inferno-i18next';
@ -76,6 +77,7 @@ export class Search extends Component<any, SearchState> {
super(props, context); super(props, context);
this.state = this.emptyState; this.state = this.emptyState;
this.handleSortChange = this.handleSortChange.bind(this);
this.subscription = WebSocketService.Instance.subject this.subscription = WebSocketService.Instance.subject
.pipe( .pipe(
@ -203,33 +205,13 @@ export class Search extends Component<any, SearchState> {
<T i18nKey="users">#</T> <T i18nKey="users">#</T>
</option> </option>
</select> </select>
<select <span class="ml-2">
value={this.state.sort} <SortSelect
onChange={linkEvent(this, this.handleSortChange)} sort={this.state.sort}
class="custom-select custom-select-sm w-auto ml-2" onChange={this.handleSortChange}
> hideHot
<option disabled> />
<T i18nKey="sort_type">#</T> </span>
</option>
<option value={SortType.New}>
<T i18nKey="new">#</T>
</option>
<option value={SortType.TopDay}>
<T i18nKey="top_day">#</T>
</option>
<option value={SortType.TopWeek}>
<T i18nKey="week">#</T>
</option>
<option value={SortType.TopMonth}>
<T i18nKey="month">#</T>
</option>
<option value={SortType.TopYear}>
<T i18nKey="year">#</T>
</option>
<option value={SortType.TopAll}>
<T i18nKey="all">#</T>
</option>
</select>
</div> </div>
); );
} }
@ -438,11 +420,11 @@ export class Search extends Component<any, SearchState> {
} }
} }
handleSortChange(i: Search, event: any) { handleSortChange(val: SortType) {
i.state.sort = Number(event.target.value); this.state.sort = val;
i.state.page = 1; this.state.page = 1;
i.setState(i.state); this.setState(this.state);
i.updateUrl(); this.updateUrl();
} }
handleTypeChange(i: Search, event: any) { handleTypeChange(i: Search, event: any) {

69
ui/src/components/sort-select.tsx vendored Normal file
View file

@ -0,0 +1,69 @@
import { Component, linkEvent } from 'inferno';
import { SortType } from '../interfaces';
import { T } from 'inferno-i18next';
interface SortSelectProps {
sort: SortType;
onChange?(val: SortType): any;
hideHot?: boolean;
}
interface SortSelectState {
sort: SortType;
}
export class SortSelect extends Component<SortSelectProps, SortSelectState> {
private emptyState: SortSelectState = {
sort: this.props.sort,
};
constructor(props: any, context: any) {
super(props, context);
this.state = this.emptyState;
}
render() {
return (
<select
value={this.state.sort}
onChange={linkEvent(this, this.handleSortChange)}
class="custom-select custom-select-sm w-auto"
>
<option disabled>
<T i18nKey="sort_type">#</T>
</option>
{!this.props.hideHot && (
<option value={SortType.Hot}>
<T i18nKey="hot">#</T>
</option>
)}
<option value={SortType.New}>
<T i18nKey="new">#</T>
</option>
<option disabled></option>
<option value={SortType.TopDay}>
<T i18nKey="top_day">#</T>
</option>
<option value={SortType.TopWeek}>
<T i18nKey="week">#</T>
</option>
<option value={SortType.TopMonth}>
<T i18nKey="month">#</T>
</option>
<option value={SortType.TopYear}>
<T i18nKey="year">#</T>
</option>
<option value={SortType.TopAll}>
<T i18nKey="all">#</T>
</option>
</select>
);
}
handleSortChange(i: SortSelect, event: any) {
i.state.sort = Number(event.target.value);
i.setState(i.state);
i.props.onChange(i.state.sort);
}
}

View file

@ -9,6 +9,7 @@ import {
CommunityUser, CommunityUser,
GetUserDetailsForm, GetUserDetailsForm,
SortType, SortType,
ListingType,
UserDetailsResponse, UserDetailsResponse,
UserView, UserView,
CommentResponse, CommentResponse,
@ -28,6 +29,8 @@ import {
setTheme, setTheme,
} from '../utils'; } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { SortSelect } from './sort-select';
import { ListingTypeSelect } from './listing-type-select';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import { MomentTime } from './moment-time'; import { MomentTime } from './moment-time';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
@ -89,6 +92,8 @@ export class User extends Component<any, UserState> {
userSettingsForm: { userSettingsForm: {
show_nsfw: null, show_nsfw: null,
theme: null, theme: null,
default_sort_type: null,
default_listing_type: null,
auth: null, auth: null,
}, },
userSettingsLoading: null, userSettingsLoading: null,
@ -103,6 +108,13 @@ export class User extends Component<any, UserState> {
super(props, context); super(props, context);
this.state = this.emptyState; this.state = this.emptyState;
this.handleSortChange = this.handleSortChange.bind(this);
this.handleUserSettingsSortTypeChange = this.handleUserSettingsSortTypeChange.bind(
this
);
this.handleUserSettingsListingTypeChange = this.handleUserSettingsListingTypeChange.bind(
this
);
this.state.user_id = Number(this.props.match.params.id); this.state.user_id = Number(this.props.match.params.id);
this.state.username = this.props.match.params.username; this.state.username = this.props.match.params.username;
@ -154,11 +166,14 @@ export class User extends Component<any, UserState> {
// Necessary for back button for some reason // Necessary for back button for some reason
componentWillReceiveProps(nextProps: any) { componentWillReceiveProps(nextProps: any) {
if (nextProps.history.action == 'POP') { if (
this.state = this.emptyState; nextProps.history.action == 'POP' ||
nextProps.history.action == 'PUSH'
) {
this.state.view = this.getViewFromProps(nextProps); this.state.view = this.getViewFromProps(nextProps);
this.state.sort = this.getSortTypeFromProps(nextProps); this.state.sort = this.getSortTypeFromProps(nextProps);
this.state.page = this.getPageFromProps(nextProps); this.state.page = this.getPageFromProps(nextProps);
this.setState(this.state);
this.refetch(); this.refetch();
} }
} }
@ -219,33 +234,13 @@ export class User extends Component<any, UserState> {
<T i18nKey="saved">#</T> <T i18nKey="saved">#</T>
</option> </option>
</select> </select>
<select <span class="ml-2">
value={this.state.sort} <SortSelect
onChange={linkEvent(this, this.handleSortChange)} sort={this.state.sort}
class="custom-select custom-select-sm w-auto ml-2" onChange={this.handleSortChange}
> hideHot
<option disabled> />
<T i18nKey="sort_type">#</T> </span>
</option>
<option value={SortType.New}>
<T i18nKey="new">#</T>
</option>
<option value={SortType.TopDay}>
<T i18nKey="top_day">#</T>
</option>
<option value={SortType.TopWeek}>
<T i18nKey="week">#</T>
</option>
<option value={SortType.TopMonth}>
<T i18nKey="month">#</T>
</option>
<option value={SortType.TopYear}>
<T i18nKey="year">#</T>
</option>
<option value={SortType.TopAll}>
<T i18nKey="all">#</T>
</option>
</select>
</div> </div>
); );
} }
@ -418,6 +413,32 @@ export class User extends Component<any, UserState> {
</select> </select>
</div> </div>
</div> </div>
<form className="form-group">
<div class="col-12">
<label>
<T i18nKey="sort_type" class="mr-2">
#
</T>
</label>
<ListingTypeSelect
type_={this.state.userSettingsForm.default_listing_type}
onChange={this.handleUserSettingsListingTypeChange}
/>
</div>
</form>
<form className="form-group">
<div class="col-12">
<label>
<T i18nKey="type" class="mr-2">
#
</T>
</label>
<SortSelect
sort={this.state.userSettingsForm.default_sort_type}
onChange={this.handleUserSettingsSortTypeChange}
/>
</div>
</form>
<div class="form-group"> <div class="form-group">
<div class="col-12"> <div class="col-12">
<div class="form-check"> <div class="form-check">
@ -436,9 +457,12 @@ export class User extends Component<any, UserState> {
</div> </div>
</div> </div>
</div> </div>
<div class="form-group row mb-0"> <div class="form-group">
<div class="col-12"> <div class="col-12">
<button type="submit" class="btn btn-secondary mr-4"> <button
type="submit"
class="btn btn-block btn-secondary mr-4"
>
{this.state.userSettingsLoading ? ( {this.state.userSettingsLoading ? (
<svg class="icon icon-spinner spin"> <svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use> <use xlinkHref="#icon-spinner"></use>
@ -447,8 +471,13 @@ export class User extends Component<any, UserState> {
capitalizeFirstLetter(i18n.t('save')) capitalizeFirstLetter(i18n.t('save'))
)} )}
</button> </button>
</div>
</div>
<hr />
<div class="form-group mb-0">
<div class="col-12">
<button <button
class="btn btn-danger" class="btn btn-block btn-danger"
onClick={linkEvent( onClick={linkEvent(
this, this,
this.handleDeleteAccountShowConfirmToggle this.handleDeleteAccountShowConfirmToggle
@ -608,12 +637,12 @@ export class User extends Component<any, UserState> {
WebSocketService.Instance.getUserDetails(form); WebSocketService.Instance.getUserDetails(form);
} }
handleSortChange(i: User, event: any) { handleSortChange(val: SortType) {
i.state.sort = Number(event.target.value); this.state.sort = val;
i.state.page = 1; this.state.page = 1;
i.setState(i.state); this.setState(this.state);
i.updateUrl(); this.updateUrl();
i.refetch(); this.refetch();
} }
handleViewChange(i: User, event: any) { handleViewChange(i: User, event: any) {
@ -635,6 +664,16 @@ export class User extends Component<any, UserState> {
i.setState(i.state); i.setState(i.state);
} }
handleUserSettingsSortTypeChange(val: SortType) {
this.state.userSettingsForm.default_sort_type = val;
this.setState(this.state);
}
handleUserSettingsListingTypeChange(val: ListingType) {
this.state.userSettingsForm.default_listing_type = val;
this.setState(this.state);
}
handleUserSettingsSubmit(i: User, event: any) { handleUserSettingsSubmit(i: User, event: any) {
event.preventDefault(); event.preventDefault();
i.state.userSettingsLoading = true; i.state.userSettingsLoading = true;
@ -685,6 +724,10 @@ export class User extends Component<any, UserState> {
this.state.userSettingsForm.theme = UserService.Instance.user.theme this.state.userSettingsForm.theme = UserService.Instance.user.theme
? UserService.Instance.user.theme ? UserService.Instance.user.theme
: 'darkly'; : 'darkly';
this.state.userSettingsForm.default_sort_type =
UserService.Instance.user.default_sort_type;
this.state.userSettingsForm.default_listing_type =
UserService.Instance.user.default_listing_type;
} }
document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`; document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
window.scrollTo(0, 0); window.scrollTo(0, 0);

View file

@ -75,6 +75,8 @@ export interface User {
username: string; username: string;
show_nsfw: boolean; show_nsfw: boolean;
theme: string; theme: string;
default_sort_type: SortType;
default_listing_type: ListingType;
} }
export interface UserView { export interface UserView {
@ -463,6 +465,8 @@ export interface LoginResponse {
export interface UserSettingsForm { export interface UserSettingsForm {
show_nsfw: boolean; show_nsfw: boolean;
theme: string; theme: string;
default_sort_type: SortType;
default_listing_type: ListingType;
auth: string; auth: string;
} }

View file

@ -53,8 +53,8 @@ export class WebSocketService {
.pipe( .pipe(
retryWhen(errors => retryWhen(errors =>
errors.pipe( errors.pipe(
delay(60000), delay(1000)
take(999) // take(999)
) )
) )
) )

2
ui/src/utils.ts vendored
View file

@ -166,6 +166,8 @@ export function routeSortTypeToEnum(sort: string): SortType {
return SortType.TopWeek; return SortType.TopWeek;
} else if (sort == 'topmonth') { } else if (sort == 'topmonth') {
return SortType.TopMonth; return SortType.TopMonth;
} else if (sort == 'topyear') {
return SortType.TopYear;
} else if (sort == 'topall') { } else if (sort == 'topall') {
return SortType.TopAll; return SortType.TopAll;
} }