Adding show_avatar user setting, and option to send notifications to inbox.
- Fixes #254 - Fixes #394
This commit is contained in:
parent
8c1316aa96
commit
e339f90737
27 changed files with 249 additions and 16 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
||||||
ansible/inventory
|
ansible/inventory
|
||||||
ansible/passwords/
|
ansible/passwords/
|
||||||
|
docker/lemmy_mine.hjson
|
||||||
build/
|
build/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
20
server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/down.sql
vendored
Normal file
20
server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/down.sql
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
-- Drop the columns
|
||||||
|
drop view user_view;
|
||||||
|
alter table user_ drop column show_avatars;
|
||||||
|
alter table user_ drop column send_notifications_to_email;
|
||||||
|
|
||||||
|
-- Rebuild the view
|
||||||
|
create view user_view as
|
||||||
|
select id,
|
||||||
|
name,
|
||||||
|
avatar,
|
||||||
|
email,
|
||||||
|
fedi_name,
|
||||||
|
admin,
|
||||||
|
banned,
|
||||||
|
published,
|
||||||
|
(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
|
||||||
|
(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
|
||||||
|
(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
|
||||||
|
(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
|
||||||
|
from user_ u;
|
22
server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/up.sql
vendored
Normal file
22
server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/up.sql
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-- Add columns
|
||||||
|
alter table user_ add column show_avatars boolean default true not null;
|
||||||
|
alter table user_ add column send_notifications_to_email boolean default false not null;
|
||||||
|
|
||||||
|
-- Rebuild the user_view
|
||||||
|
drop view user_view;
|
||||||
|
create view user_view as
|
||||||
|
select id,
|
||||||
|
name,
|
||||||
|
avatar,
|
||||||
|
email,
|
||||||
|
fedi_name,
|
||||||
|
admin,
|
||||||
|
banned,
|
||||||
|
show_avatars,
|
||||||
|
send_notifications_to_email,
|
||||||
|
published,
|
||||||
|
(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
|
||||||
|
(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
|
||||||
|
(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
|
||||||
|
(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
|
||||||
|
from user_ u;
|
|
@ -1,4 +1,6 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::send_email;
|
||||||
|
use crate::settings::Settings;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct CreateComment {
|
pub struct CreateComment {
|
||||||
|
@ -56,6 +58,8 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
|
||||||
|
|
||||||
let user_id = claims.id;
|
let user_id = claims.id;
|
||||||
|
|
||||||
|
let hostname = &format!("https://{}", Settings::get().hostname);
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
let post = Post::read(&conn, data.post_id)?;
|
let post = Post::read(&conn, data.post_id)?;
|
||||||
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
|
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
|
||||||
|
@ -89,17 +93,13 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
|
||||||
let extracted_usernames = extract_usernames(&comment_form.content);
|
let extracted_usernames = extract_usernames(&comment_form.content);
|
||||||
|
|
||||||
for username_mention in &extracted_usernames {
|
for username_mention in &extracted_usernames {
|
||||||
let mention_user = User_::read_from_name(&conn, (*username_mention).to_string());
|
if let Ok(mention_user) = User_::read_from_name(&conn, (*username_mention).to_string()) {
|
||||||
|
|
||||||
if mention_user.is_ok() {
|
|
||||||
let mention_user_id = mention_user?.id;
|
|
||||||
|
|
||||||
// You can't mention yourself
|
// You can't mention yourself
|
||||||
// At some point, make it so you can't tag the parent creator either
|
// At some point, make it so you can't tag the parent creator either
|
||||||
// This can cause two notifications, one for reply and the other for mention
|
// This can cause two notifications, one for reply and the other for mention
|
||||||
if mention_user_id != user_id {
|
if mention_user.id != user_id {
|
||||||
let user_mention_form = UserMentionForm {
|
let user_mention_form = UserMentionForm {
|
||||||
recipient_id: mention_user_id,
|
recipient_id: mention_user.id,
|
||||||
comment_id: inserted_comment.id,
|
comment_id: inserted_comment.id,
|
||||||
read: None,
|
read: None,
|
||||||
};
|
};
|
||||||
|
@ -109,10 +109,75 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
|
||||||
match UserMention::create(&conn, &user_mention_form) {
|
match UserMention::create(&conn, &user_mention_form) {
|
||||||
Ok(_mention) => (),
|
Ok(_mention) => (),
|
||||||
Err(_e) => eprintln!("{}", &_e),
|
Err(_e) => eprintln!("{}", &_e),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send an email to those users that have notifications on
|
||||||
|
if mention_user.send_notifications_to_email {
|
||||||
|
if let Some(mention_email) = mention_user.email {
|
||||||
|
let subject = &format!(
|
||||||
|
"{} - Mentioned by {}",
|
||||||
|
Settings::get().hostname,
|
||||||
|
claims.username
|
||||||
|
);
|
||||||
|
let html = &format!(
|
||||||
|
"<h1>User Mention</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
|
||||||
|
claims.username, comment_form.content, hostname
|
||||||
|
);
|
||||||
|
match send_email(subject, &mention_email, &mention_user.name, html) {
|
||||||
|
Ok(_o) => _o,
|
||||||
|
Err(e) => eprintln!("{}", e),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send notifs to the parent commenter / poster
|
||||||
|
match data.parent_id {
|
||||||
|
Some(parent_id) => {
|
||||||
|
let parent_comment = Comment::read(&conn, parent_id)?;
|
||||||
|
let parent_user = User_::read(&conn, parent_comment.creator_id)?;
|
||||||
|
if parent_user.send_notifications_to_email {
|
||||||
|
if let Some(comment_reply_email) = parent_user.email {
|
||||||
|
let subject = &format!(
|
||||||
|
"{} - Reply from {}",
|
||||||
|
Settings::get().hostname,
|
||||||
|
claims.username
|
||||||
|
);
|
||||||
|
let html = &format!(
|
||||||
|
"<h1>Comment Reply</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
|
||||||
|
claims.username, comment_form.content, hostname
|
||||||
|
);
|
||||||
|
match send_email(subject, &comment_reply_email, &parent_user.name, html) {
|
||||||
|
Ok(_o) => _o,
|
||||||
|
Err(e) => eprintln!("{}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Its a post
|
||||||
|
None => {
|
||||||
|
let parent_user = User_::read(&conn, post.creator_id)?;
|
||||||
|
if parent_user.send_notifications_to_email {
|
||||||
|
if let Some(post_reply_email) = parent_user.email {
|
||||||
|
let subject = &format!(
|
||||||
|
"{} - Reply from {}",
|
||||||
|
Settings::get().hostname,
|
||||||
|
claims.username
|
||||||
|
);
|
||||||
|
let html = &format!(
|
||||||
|
"<h1>Post Reply</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
|
||||||
|
claims.username, comment_form.content, hostname
|
||||||
|
);
|
||||||
|
match send_email(subject, &post_reply_email, &parent_user.name, html) {
|
||||||
|
Ok(_o) => _o,
|
||||||
|
Err(e) => eprintln!("{}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// You like your own comment by default
|
// You like your own comment by default
|
||||||
let like_form = CommentLikeForm {
|
let like_form = CommentLikeForm {
|
||||||
|
|
|
@ -32,6 +32,8 @@ pub struct SaveUserSettings {
|
||||||
new_password: Option<String>,
|
new_password: Option<String>,
|
||||||
new_password_verify: Option<String>,
|
new_password_verify: Option<String>,
|
||||||
old_password: Option<String>,
|
old_password: Option<String>,
|
||||||
|
show_avatars: bool,
|
||||||
|
send_notifications_to_email: bool,
|
||||||
auth: String,
|
auth: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,6 +233,8 @@ impl Perform<LoginResponse> for Oper<Register> {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the user
|
// Create the user
|
||||||
|
@ -356,6 +360,8 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
|
||||||
default_sort_type: data.default_sort_type,
|
default_sort_type: data.default_sort_type,
|
||||||
default_listing_type: data.default_listing_type,
|
default_listing_type: data.default_listing_type,
|
||||||
lang: data.lang.to_owned(),
|
lang: data.lang.to_owned(),
|
||||||
|
show_avatars: data.show_avatars,
|
||||||
|
send_notifications_to_email: data.send_notifications_to_email,
|
||||||
};
|
};
|
||||||
|
|
||||||
let updated_user = match User_::update(&conn, user_id, &user_form) {
|
let updated_user = match User_::update(&conn, user_id, &user_form) {
|
||||||
|
@ -497,6 +503,8 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
|
||||||
default_sort_type: read_user.default_sort_type,
|
default_sort_type: read_user.default_sort_type,
|
||||||
default_listing_type: read_user.default_listing_type,
|
default_listing_type: read_user.default_listing_type,
|
||||||
lang: read_user.lang,
|
lang: read_user.lang,
|
||||||
|
show_avatars: read_user.show_avatars,
|
||||||
|
send_notifications_to_email: read_user.send_notifications_to_email,
|
||||||
};
|
};
|
||||||
|
|
||||||
match User_::update(&conn, data.user_id, &user_form) {
|
match User_::update(&conn, data.user_id, &user_form) {
|
||||||
|
@ -560,6 +568,8 @@ impl Perform<BanUserResponse> for Oper<BanUser> {
|
||||||
default_sort_type: read_user.default_sort_type,
|
default_sort_type: read_user.default_sort_type,
|
||||||
default_listing_type: read_user.default_listing_type,
|
default_listing_type: read_user.default_listing_type,
|
||||||
lang: read_user.lang,
|
lang: read_user.lang,
|
||||||
|
show_avatars: read_user.show_avatars,
|
||||||
|
send_notifications_to_email: read_user.send_notifications_to_email,
|
||||||
};
|
};
|
||||||
|
|
||||||
match User_::update(&conn, data.user_id, &user_form) {
|
match User_::update(&conn, data.user_id, &user_form) {
|
||||||
|
|
|
@ -32,6 +32,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let person = user.as_person();
|
let person = user.as_person();
|
||||||
|
|
|
@ -183,6 +183,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
|
@ -381,6 +381,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
|
@ -229,6 +229,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
|
@ -451,6 +451,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_mod = User_::create(&conn, &new_mod).unwrap();
|
let inserted_mod = User_::create(&conn, &new_mod).unwrap();
|
||||||
|
@ -470,6 +472,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
|
@ -101,6 +101,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
|
@ -196,6 +196,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
|
@ -311,6 +311,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
|
|
@ -24,6 +24,8 @@ pub struct User_ {
|
||||||
pub default_sort_type: i16,
|
pub default_sort_type: i16,
|
||||||
pub default_listing_type: i16,
|
pub default_listing_type: i16,
|
||||||
pub lang: String,
|
pub lang: String,
|
||||||
|
pub show_avatars: bool,
|
||||||
|
pub send_notifications_to_email: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable, AsChangeset, Clone)]
|
#[derive(Insertable, AsChangeset, Clone)]
|
||||||
|
@ -43,6 +45,8 @@ pub struct UserForm {
|
||||||
pub default_sort_type: i16,
|
pub default_sort_type: i16,
|
||||||
pub default_listing_type: i16,
|
pub default_listing_type: i16,
|
||||||
pub lang: String,
|
pub lang: String,
|
||||||
|
pub show_avatars: bool,
|
||||||
|
pub send_notifications_to_email: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Crud<UserForm> for User_ {
|
impl Crud<UserForm> for User_ {
|
||||||
|
@ -100,6 +104,7 @@ pub struct Claims {
|
||||||
pub default_listing_type: i16,
|
pub default_listing_type: i16,
|
||||||
pub lang: String,
|
pub lang: String,
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
|
pub show_avatars: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Claims {
|
impl Claims {
|
||||||
|
@ -125,6 +130,7 @@ impl User_ {
|
||||||
default_listing_type: self.default_listing_type,
|
default_listing_type: self.default_listing_type,
|
||||||
lang: self.lang.to_owned(),
|
lang: self.lang.to_owned(),
|
||||||
avatar: self.avatar.to_owned(),
|
avatar: self.avatar.to_owned(),
|
||||||
|
show_avatars: self.show_avatars.to_owned(),
|
||||||
};
|
};
|
||||||
encode(
|
encode(
|
||||||
&Header::default(),
|
&Header::default(),
|
||||||
|
@ -187,6 +193,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
@ -208,6 +216,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let read_user = User_::read(&conn, inserted_user.id).unwrap();
|
let read_user = User_::read(&conn, inserted_user.id).unwrap();
|
||||||
|
|
|
@ -77,6 +77,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||||
|
@ -96,6 +98,8 @@ mod tests {
|
||||||
default_sort_type: SortType::Hot as i16,
|
default_sort_type: SortType::Hot as i16,
|
||||||
default_listing_type: ListingType::Subscribed as i16,
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
lang: "browser".into(),
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
|
let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
|
||||||
|
|
|
@ -11,6 +11,8 @@ table! {
|
||||||
fedi_name -> Varchar,
|
fedi_name -> Varchar,
|
||||||
admin -> Bool,
|
admin -> Bool,
|
||||||
banned -> Bool,
|
banned -> Bool,
|
||||||
|
show_avatars -> Bool,
|
||||||
|
send_notifications_to_email -> Bool,
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
number_of_posts -> BigInt,
|
number_of_posts -> BigInt,
|
||||||
post_score -> BigInt,
|
post_score -> BigInt,
|
||||||
|
@ -31,6 +33,8 @@ pub struct UserView {
|
||||||
pub fedi_name: String,
|
pub fedi_name: String,
|
||||||
pub admin: bool,
|
pub admin: bool,
|
||||||
pub banned: bool,
|
pub banned: bool,
|
||||||
|
pub show_avatars: bool,
|
||||||
|
pub send_notifications_to_email: bool,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
pub number_of_posts: i64,
|
pub number_of_posts: i64,
|
||||||
pub post_score: i64,
|
pub post_score: i64,
|
||||||
|
|
|
@ -270,6 +270,8 @@ table! {
|
||||||
default_sort_type -> Int2,
|
default_sort_type -> Int2,
|
||||||
default_listing_type -> Int2,
|
default_listing_type -> Int2,
|
||||||
lang -> Varchar,
|
lang -> Varchar,
|
||||||
|
show_avatars -> Bool,
|
||||||
|
send_notifications_to_email -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
ui/src/components/comment-node.tsx
vendored
3
ui/src/components/comment-node.tsx
vendored
|
@ -23,6 +23,7 @@ import {
|
||||||
canMod,
|
canMod,
|
||||||
isMod,
|
isMod,
|
||||||
pictshareAvatarThumbnail,
|
pictshareAvatarThumbnail,
|
||||||
|
showAvatars,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { MomentTime } from './moment-time';
|
import { MomentTime } from './moment-time';
|
||||||
|
@ -138,7 +139,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="text-info"
|
className="text-info"
|
||||||
to={`/u/${node.comment.creator_name}`}
|
to={`/u/${node.comment.creator_name}`}
|
||||||
>
|
>
|
||||||
{node.comment.creator_avatar && (
|
{node.comment.creator_avatar && showAvatars() && (
|
||||||
<img
|
<img
|
||||||
height="32"
|
height="32"
|
||||||
width="32"
|
width="32"
|
||||||
|
|
3
ui/src/components/main.tsx
vendored
3
ui/src/components/main.tsx
vendored
|
@ -32,6 +32,7 @@ import {
|
||||||
routeListingTypeToEnum,
|
routeListingTypeToEnum,
|
||||||
postRefetchSeconds,
|
postRefetchSeconds,
|
||||||
pictshareAvatarThumbnail,
|
pictshareAvatarThumbnail,
|
||||||
|
showAvatars,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
@ -345,7 +346,7 @@ export class Main extends Component<any, MainState> {
|
||||||
{this.state.site.admins.map(admin => (
|
{this.state.site.admins.map(admin => (
|
||||||
<li class="list-inline-item">
|
<li class="list-inline-item">
|
||||||
<Link class="text-info" to={`/u/${admin.name}`}>
|
<Link class="text-info" to={`/u/${admin.name}`}>
|
||||||
{admin.avatar && (
|
{admin.avatar && showAvatars() && (
|
||||||
<img
|
<img
|
||||||
height="32"
|
height="32"
|
||||||
width="32"
|
width="32"
|
||||||
|
|
4
ui/src/components/navbar.tsx
vendored
4
ui/src/components/navbar.tsx
vendored
|
@ -13,7 +13,7 @@ import {
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
Comment,
|
Comment,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { msgOp, pictshareAvatarThumbnail } from '../utils';
|
import { msgOp, pictshareAvatarThumbnail, showAvatars } from '../utils';
|
||||||
import { version } from '../version';
|
import { version } from '../version';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
@ -152,7 +152,7 @@ export class Navbar extends Component<any, NavbarState> {
|
||||||
to={`/u/${UserService.Instance.user.username}`}
|
to={`/u/${UserService.Instance.user.username}`}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{UserService.Instance.user.avatar && (
|
{UserService.Instance.user.avatar && showAvatars() && (
|
||||||
<img
|
<img
|
||||||
src={pictshareAvatarThumbnail(
|
src={pictshareAvatarThumbnail(
|
||||||
UserService.Instance.user.avatar
|
UserService.Instance.user.avatar
|
||||||
|
|
3
ui/src/components/post-listing.tsx
vendored
3
ui/src/components/post-listing.tsx
vendored
|
@ -26,6 +26,7 @@ import {
|
||||||
isVideo,
|
isVideo,
|
||||||
getUnixTime,
|
getUnixTime,
|
||||||
pictshareAvatarThumbnail,
|
pictshareAvatarThumbnail,
|
||||||
|
showAvatars,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
@ -249,7 +250,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
<span>{i18n.t('by')} </span>
|
<span>{i18n.t('by')} </span>
|
||||||
<Link className="text-info" to={`/u/${post.creator_name}`}>
|
<Link className="text-info" to={`/u/${post.creator_name}`}>
|
||||||
{post.creator_avatar && (
|
{post.creator_avatar && showAvatars() && (
|
||||||
<img
|
<img
|
||||||
height="32"
|
height="32"
|
||||||
width="32"
|
width="32"
|
||||||
|
|
3
ui/src/components/search.tsx
vendored
3
ui/src/components/search.tsx
vendored
|
@ -20,6 +20,7 @@ import {
|
||||||
routeSearchTypeToEnum,
|
routeSearchTypeToEnum,
|
||||||
routeSortTypeToEnum,
|
routeSortTypeToEnum,
|
||||||
pictshareAvatarThumbnail,
|
pictshareAvatarThumbnail,
|
||||||
|
showAvatars,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { PostListing } from './post-listing';
|
import { PostListing } from './post-listing';
|
||||||
import { SortSelect } from './sort-select';
|
import { SortSelect } from './sort-select';
|
||||||
|
@ -288,7 +289,7 @@ export class Search extends Component<any, SearchState> {
|
||||||
className="text-info"
|
className="text-info"
|
||||||
to={`/u/${(i.data as UserView).name}`}
|
to={`/u/${(i.data as UserView).name}`}
|
||||||
>
|
>
|
||||||
{(i.data as UserView).avatar && (
|
{(i.data as UserView).avatar && showAvatars() && (
|
||||||
<img
|
<img
|
||||||
height="32"
|
height="32"
|
||||||
width="32"
|
width="32"
|
||||||
|
|
9
ui/src/components/sidebar.tsx
vendored
9
ui/src/components/sidebar.tsx
vendored
|
@ -8,7 +8,12 @@ import {
|
||||||
UserView,
|
UserView,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { mdToHtml, getUnixTime, pictshareAvatarThumbnail } from '../utils';
|
import {
|
||||||
|
mdToHtml,
|
||||||
|
getUnixTime,
|
||||||
|
pictshareAvatarThumbnail,
|
||||||
|
showAvatars,
|
||||||
|
} from '../utils';
|
||||||
import { CommunityForm } from './community-form';
|
import { CommunityForm } from './community-form';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
@ -194,7 +199,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
{this.props.moderators.map(mod => (
|
{this.props.moderators.map(mod => (
|
||||||
<li class="list-inline-item">
|
<li class="list-inline-item">
|
||||||
<Link class="text-info" to={`/u/${mod.user_name}`}>
|
<Link class="text-info" to={`/u/${mod.user_name}`}>
|
||||||
{mod.avatar && (
|
{mod.avatar && showAvatars() && (
|
||||||
<img
|
<img
|
||||||
height="32"
|
height="32"
|
||||||
width="32"
|
width="32"
|
||||||
|
|
56
ui/src/components/user.tsx
vendored
56
ui/src/components/user.tsx
vendored
|
@ -28,6 +28,7 @@ import {
|
||||||
themes,
|
themes,
|
||||||
setTheme,
|
setTheme,
|
||||||
languages,
|
languages,
|
||||||
|
showAvatars,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { PostListing } from './post-listing';
|
import { PostListing } from './post-listing';
|
||||||
import { SortSelect } from './sort-select';
|
import { SortSelect } from './sort-select';
|
||||||
|
@ -80,6 +81,8 @@ export class User extends Component<any, UserState> {
|
||||||
comment_score: null,
|
comment_score: null,
|
||||||
banned: null,
|
banned: null,
|
||||||
avatar: null,
|
avatar: null,
|
||||||
|
show_avatars: null,
|
||||||
|
send_notifications_to_email: null,
|
||||||
},
|
},
|
||||||
user_id: null,
|
user_id: null,
|
||||||
username: null,
|
username: null,
|
||||||
|
@ -99,6 +102,8 @@ export class User extends Component<any, UserState> {
|
||||||
default_sort_type: null,
|
default_sort_type: null,
|
||||||
default_listing_type: null,
|
default_listing_type: null,
|
||||||
lang: null,
|
lang: null,
|
||||||
|
show_avatars: null,
|
||||||
|
send_notifications_to_email: null,
|
||||||
auth: null,
|
auth: null,
|
||||||
},
|
},
|
||||||
userSettingsLoading: null,
|
userSettingsLoading: null,
|
||||||
|
@ -207,7 +212,7 @@ export class User extends Component<any, UserState> {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-8">
|
<div class="col-12 col-md-8">
|
||||||
<h5>
|
<h5>
|
||||||
{this.state.user.avatar && (
|
{this.state.user.avatar && showAvatars() && (
|
||||||
<img
|
<img
|
||||||
height="80"
|
height="80"
|
||||||
width="80"
|
width="80"
|
||||||
|
@ -610,6 +615,41 @@ export class User extends Component<any, UserState> {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
checked={this.state.userSettingsForm.show_avatars}
|
||||||
|
onChange={linkEvent(
|
||||||
|
this,
|
||||||
|
this.handleUserSettingsShowAvatarsChange
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<label class="form-check-label">
|
||||||
|
<T i18nKey="show_avatars">#</T>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
disabled={!this.state.user.email}
|
||||||
|
checked={
|
||||||
|
this.state.userSettingsForm.send_notifications_to_email
|
||||||
|
}
|
||||||
|
onChange={linkEvent(
|
||||||
|
this,
|
||||||
|
this.handleUserSettingsSendNotificationsToEmailChange
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<label class="form-check-label">
|
||||||
|
<T i18nKey="send_notifications_to_email">#</T>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<button type="submit" class="btn btn-block btn-secondary mr-4">
|
<button type="submit" class="btn btn-block btn-secondary mr-4">
|
||||||
{this.state.userSettingsLoading ? (
|
{this.state.userSettingsLoading ? (
|
||||||
|
@ -804,6 +844,17 @@ export class User extends Component<any, UserState> {
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleUserSettingsShowAvatarsChange(i: User, event: any) {
|
||||||
|
i.state.userSettingsForm.show_avatars = event.target.checked;
|
||||||
|
UserService.Instance.user.show_avatars = event.target.checked; // Just for instant updates
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUserSettingsSendNotificationsToEmailChange(i: User, event: any) {
|
||||||
|
i.state.userSettingsForm.send_notifications_to_email = event.target.checked;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
handleUserSettingsThemeChange(i: User, event: any) {
|
handleUserSettingsThemeChange(i: User, event: any) {
|
||||||
i.state.userSettingsForm.theme = event.target.value;
|
i.state.userSettingsForm.theme = event.target.value;
|
||||||
setTheme(event.target.value);
|
setTheme(event.target.value);
|
||||||
|
@ -957,6 +1008,9 @@ export class User extends Component<any, UserState> {
|
||||||
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
|
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
|
||||||
this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
|
this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
|
||||||
this.state.userSettingsForm.email = this.state.user.email;
|
this.state.userSettingsForm.email = this.state.user.email;
|
||||||
|
this.state.userSettingsForm.send_notifications_to_email = this.state.user.send_notifications_to_email;
|
||||||
|
this.state.userSettingsForm.show_avatars =
|
||||||
|
UserService.Instance.user.show_avatars;
|
||||||
}
|
}
|
||||||
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);
|
||||||
|
|
5
ui/src/interfaces.ts
vendored
5
ui/src/interfaces.ts
vendored
|
@ -81,6 +81,7 @@ export interface User {
|
||||||
default_listing_type: ListingType;
|
default_listing_type: ListingType;
|
||||||
lang: string;
|
lang: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
show_avatars: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserView {
|
export interface UserView {
|
||||||
|
@ -95,6 +96,8 @@ export interface UserView {
|
||||||
number_of_comments: number;
|
number_of_comments: number;
|
||||||
comment_score: number;
|
comment_score: number;
|
||||||
banned: boolean;
|
banned: boolean;
|
||||||
|
show_avatars: boolean;
|
||||||
|
send_notifications_to_email: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommunityUser {
|
export interface CommunityUser {
|
||||||
|
@ -486,6 +489,8 @@ export interface UserSettingsForm {
|
||||||
new_password?: string;
|
new_password?: string;
|
||||||
new_password_verify?: string;
|
new_password_verify?: string;
|
||||||
old_password?: string;
|
old_password?: string;
|
||||||
|
show_avatars: boolean;
|
||||||
|
send_notifications_to_email: boolean;
|
||||||
auth: string;
|
auth: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
ui/src/translations/en.ts
vendored
2
ui/src/translations/en.ts
vendored
|
@ -29,6 +29,7 @@ export const en = {
|
||||||
preview: 'Preview',
|
preview: 'Preview',
|
||||||
upload_image: 'upload image',
|
upload_image: 'upload image',
|
||||||
avatar: 'Avatar',
|
avatar: 'Avatar',
|
||||||
|
show_avatars: 'Show Avatars',
|
||||||
formatting_help: 'formatting help',
|
formatting_help: 'formatting help',
|
||||||
view_source: 'view source',
|
view_source: 'view source',
|
||||||
unlock: 'unlock',
|
unlock: 'unlock',
|
||||||
|
@ -126,6 +127,7 @@ export const en = {
|
||||||
new_password: 'New Password',
|
new_password: 'New Password',
|
||||||
no_email_setup: "This server hasn't correctly set up email.",
|
no_email_setup: "This server hasn't correctly set up email.",
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
|
send_notifications_to_email: 'Send notifications to Email',
|
||||||
optional: 'Optional',
|
optional: 'Optional',
|
||||||
expires: 'Expires',
|
expires: 'Expires',
|
||||||
language: 'Language',
|
language: 'Language',
|
||||||
|
|
7
ui/src/utils.ts
vendored
7
ui/src/utils.ts
vendored
|
@ -345,3 +345,10 @@ export function pictshareAvatarThumbnail(src: string): string {
|
||||||
let out = `${split[0]}pictshare/96x96${split[1]}`;
|
let out = `${split[0]}pictshare/96x96${split[1]}`;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showAvatars(): boolean {
|
||||||
|
return (
|
||||||
|
(UserService.Instance.user && UserService.Instance.user.show_avatars) ||
|
||||||
|
!UserService.Instance.user
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Reference in a new issue