Adding show_avatar user setting, and option to send notifications to inbox.

- Fixes #254
- Fixes #394
This commit is contained in:
Dessalines 2020-01-02 16:55:54 -05:00
parent 576980bf64
commit 5b42dc3393
27 changed files with 249 additions and 16 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
ansible/inventory ansible/inventory
ansible/passwords/ ansible/passwords/
docker/lemmy_mine.hjson
build/ build/
.idea/ .idea/

View 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;

View 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;

View File

@ -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,11 +109,76 @@ 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 {
comment_id: inserted_comment.id, comment_id: inserted_comment.id,

View File

@ -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) {

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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,

View File

@ -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,
} }
} }

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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);

View File

@ -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;
} }

View File

@ -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
View File

@ -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
);
}