]> Untitled Git - lemmy.git/commitdiff
Adding show_avatar user setting, and option to send notifications to inbox.
authorDessalines <tyhou13@gmx.com>
Thu, 2 Jan 2020 21:55:54 +0000 (16:55 -0500)
committerDessalines <tyhou13@gmx.com>
Thu, 2 Jan 2020 21:55:54 +0000 (16:55 -0500)
- Fixes #254
- Fixes #394

27 files changed:
.gitignore
server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/down.sql [new file with mode: 0644]
server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/up.sql [new file with mode: 0644]
server/src/api/comment.rs
server/src/api/user.rs
server/src/apub/mod.rs
server/src/db/comment.rs
server/src/db/comment_view.rs
server/src/db/community.rs
server/src/db/moderator.rs
server/src/db/password_reset_request.rs
server/src/db/post.rs
server/src/db/post_view.rs
server/src/db/user.rs
server/src/db/user_mention.rs
server/src/db/user_view.rs
server/src/schema.rs
ui/src/components/comment-node.tsx
ui/src/components/main.tsx
ui/src/components/navbar.tsx
ui/src/components/post-listing.tsx
ui/src/components/search.tsx
ui/src/components/sidebar.tsx
ui/src/components/user.tsx
ui/src/interfaces.ts
ui/src/translations/en.ts
ui/src/utils.ts

index 4cb8939f5f67c82a2011fa209332ca0f4b444ffe..16548f36c615c3cbc33699d769c8edf5b7125c38 100644 (file)
@@ -1,4 +1,5 @@
 ansible/inventory
 ansible/passwords/
+docker/lemmy_mine.hjson
 build/
 .idea/
diff --git a/server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/down.sql b/server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/down.sql
new file mode 100644 (file)
index 0000000..ec06122
--- /dev/null
@@ -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;
diff --git a/server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/up.sql b/server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/up.sql
new file mode 100644 (file)
index 0000000..21f0aff
--- /dev/null
@@ -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;
index ed658985cc0225f2fc437082af75abf68012e75d..5c3417dd6ed53f825954f42e785f5f9870ead6ea 100644 (file)
@@ -1,4 +1,6 @@
 use super::*;
+use crate::send_email;
+use crate::settings::Settings;
 
 #[derive(Serialize, Deserialize)]
 pub struct CreateComment {
@@ -56,6 +58,8 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
 
     let user_id = claims.id;
 
+    let hostname = &format!("https://{}", Settings::get().hostname);
+
     // Check for a community ban
     let post = Post::read(&conn, data.post_id)?;
     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);
 
     for username_mention in &extracted_usernames {
-      let mention_user = User_::read_from_name(&conn, (*username_mention).to_string());
-
-      if mention_user.is_ok() {
-        let mention_user_id = mention_user?.id;
-
+      if let Ok(mention_user) = User_::read_from_name(&conn, (*username_mention).to_string()) {
         // You can't mention yourself
         // 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
-        if mention_user_id != user_id {
+        if mention_user.id != user_id {
           let user_mention_form = UserMentionForm {
-            recipient_id: mention_user_id,
+            recipient_id: mention_user.id,
             comment_id: inserted_comment.id,
             read: None,
           };
@@ -109,11 +109,76 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
           match UserMention::create(&conn, &user_mention_form) {
             Ok(_mention) => (),
             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
     let like_form = CommentLikeForm {
       comment_id: inserted_comment.id,
index 912587da3870c88816659bafd037fb5a70fa1ef1..41729eb756196c212b0c6a82549d940cd08eda1e 100644 (file)
@@ -32,6 +32,8 @@ pub struct SaveUserSettings {
   new_password: Option<String>,
   new_password_verify: Option<String>,
   old_password: Option<String>,
+  show_avatars: bool,
+  send_notifications_to_email: bool,
   auth: String,
 }
 
@@ -231,6 +233,8 @@ impl Perform<LoginResponse> for Oper<Register> {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     // Create the user
@@ -356,6 +360,8 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
       default_sort_type: data.default_sort_type,
       default_listing_type: data.default_listing_type,
       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) {
@@ -497,6 +503,8 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
       default_sort_type: read_user.default_sort_type,
       default_listing_type: read_user.default_listing_type,
       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) {
@@ -560,6 +568,8 @@ impl Perform<BanUserResponse> for Oper<BanUser> {
       default_sort_type: read_user.default_sort_type,
       default_listing_type: read_user.default_listing_type,
       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) {
index b1a01b6d530ebbd3c176147b672bcb3cd565e88e..2d2e5ad301c8078f972bd5e4d67e320288579795 100644 (file)
@@ -32,6 +32,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let person = user.as_person();
index 6419535619c6da791419622e5f268b029b08c4eb..b319805225f3920749d161384cabc9ecf95ea90a 100644 (file)
@@ -183,6 +183,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let inserted_user = User_::create(&conn, &new_user).unwrap();
index c56da51da973059dec1fcb3f83933aab16922d98..2942bbe746ac104723f26986ccc936d9a7a84d2e 100644 (file)
@@ -381,6 +381,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let inserted_user = User_::create(&conn, &new_user).unwrap();
index b5d0538443f979250f2a33a018e5e88abf3a7da9..09c3ddc4ff167d408dd8aead3ad4e0d97b9d3d40 100644 (file)
@@ -229,6 +229,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let inserted_user = User_::create(&conn, &new_user).unwrap();
index 7f1c3499c1b4f3653d29c67efca1803ce3250dbf..dc018bd93957ccbb753c87c92acdc18457383368 100644 (file)
@@ -451,6 +451,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let inserted_mod = User_::create(&conn, &new_mod).unwrap();
@@ -470,6 +472,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let inserted_user = User_::create(&conn, &new_user).unwrap();
index b7983f535e148e40937828de79f6d2c500536320..1664516b36bd896b2c4e44b5c921bbb2ba84c06c 100644 (file)
@@ -101,6 +101,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let inserted_user = User_::create(&conn, &new_user).unwrap();
index da669ea13423c64b9f4e43c6a830613a62ca43dc..084edc9bf3867013f127fa75529f4c01fea7127a 100644 (file)
@@ -196,6 +196,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let inserted_user = User_::create(&conn, &new_user).unwrap();
index 22e2eb14e2e2f5ba6b99bc4bea4f417ec9cc97fb..f0638eb530b9a78fc1c19ede4f22a225d173e93a 100644 (file)
@@ -311,6 +311,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let inserted_user = User_::create(&conn, &new_user).unwrap();
index 82736f8e2457869464e6ddcfc4e0eb856fc4d599..b04012974128a427b6b8912986d032f96af9453d 100644 (file)
@@ -24,6 +24,8 @@ pub struct User_ {
   pub default_sort_type: i16,
   pub default_listing_type: i16,
   pub lang: String,
+  pub show_avatars: bool,
+  pub send_notifications_to_email: bool,
 }
 
 #[derive(Insertable, AsChangeset, Clone)]
@@ -43,6 +45,8 @@ pub struct UserForm {
   pub default_sort_type: i16,
   pub default_listing_type: i16,
   pub lang: String,
+  pub show_avatars: bool,
+  pub send_notifications_to_email: bool,
 }
 
 impl Crud<UserForm> for User_ {
@@ -100,6 +104,7 @@ pub struct Claims {
   pub default_listing_type: i16,
   pub lang: String,
   pub avatar: Option<String>,
+  pub show_avatars: bool,
 }
 
 impl Claims {
@@ -125,6 +130,7 @@ impl User_ {
       default_listing_type: self.default_listing_type,
       lang: self.lang.to_owned(),
       avatar: self.avatar.to_owned(),
+      show_avatars: self.show_avatars.to_owned(),
     };
     encode(
       &Header::default(),
@@ -187,6 +193,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let inserted_user = User_::create(&conn, &new_user).unwrap();
@@ -208,6 +216,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let read_user = User_::read(&conn, inserted_user.id).unwrap();
index 5392c87a23083990742aa99662d91c776ee7f8de..67286779769206de8cda5242567d67d89a9aedb1 100644 (file)
@@ -77,6 +77,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let inserted_user = User_::create(&conn, &new_user).unwrap();
@@ -96,6 +98,8 @@ mod tests {
       default_sort_type: SortType::Hot as i16,
       default_listing_type: ListingType::Subscribed as i16,
       lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
     };
 
     let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
index 2d50b40c44f4ef1740531a463514ec4ef26da63e..e0de223073e81ecb5081c613784c22851a394a0b 100644 (file)
@@ -11,6 +11,8 @@ table! {
     fedi_name -> Varchar,
     admin -> Bool,
     banned -> Bool,
+    show_avatars -> Bool,
+    send_notifications_to_email -> Bool,
     published -> Timestamp,
     number_of_posts -> BigInt,
     post_score -> BigInt,
@@ -31,6 +33,8 @@ pub struct UserView {
   pub fedi_name: String,
   pub admin: bool,
   pub banned: bool,
+  pub show_avatars: bool,
+  pub send_notifications_to_email: bool,
   pub published: chrono::NaiveDateTime,
   pub number_of_posts: i64,
   pub post_score: i64,
index 86834072c85a5bbd4203f3d6e595200394c16014..61957067c51b2a071d69dad9cc320dc3221e9a10 100644 (file)
@@ -270,6 +270,8 @@ table! {
         default_sort_type -> Int2,
         default_listing_type -> Int2,
         lang -> Varchar,
+        show_avatars -> Bool,
+        send_notifications_to_email -> Bool,
     }
 }
 
index b3ca682b2b06c78fac48376e64f4921aab7b7e0c..64bc7134b09587c861f7e39b328b6966e3069392 100644 (file)
@@ -23,6 +23,7 @@ import {
   canMod,
   isMod,
   pictshareAvatarThumbnail,
+  showAvatars,
 } from '../utils';
 import * as moment from 'moment';
 import { MomentTime } from './moment-time';
@@ -138,7 +139,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                 className="text-info"
                 to={`/u/${node.comment.creator_name}`}
               >
-                {node.comment.creator_avatar && (
+                {node.comment.creator_avatar && showAvatars() && (
                   <img
                     height="32"
                     width="32"
index d1042f384b6b2aa6084f3d2df0684225aaeadf3d..373889f9b0a319e475c07759b114e7e1217e116a 100644 (file)
@@ -32,6 +32,7 @@ import {
   routeListingTypeToEnum,
   postRefetchSeconds,
   pictshareAvatarThumbnail,
+  showAvatars,
 } from '../utils';
 import { i18n } from '../i18next';
 import { T } from 'inferno-i18next';
@@ -345,7 +346,7 @@ export class Main extends Component<any, MainState> {
               {this.state.site.admins.map(admin => (
                 <li class="list-inline-item">
                   <Link class="text-info" to={`/u/${admin.name}`}>
-                    {admin.avatar && (
+                    {admin.avatar && showAvatars() && (
                       <img
                         height="32"
                         width="32"
index f1a989419da9f7d3f4f7347cc20772b078645041..f1c35b1f5396d8c781089f11a0ed18f13f171605 100644 (file)
@@ -13,7 +13,7 @@ import {
   GetSiteResponse,
   Comment,
 } from '../interfaces';
-import { msgOp, pictshareAvatarThumbnail } from '../utils';
+import { msgOp, pictshareAvatarThumbnail, showAvatars } from '../utils';
 import { version } from '../version';
 import { i18n } from '../i18next';
 import { T } from 'inferno-i18next';
@@ -152,7 +152,7 @@ export class Navbar extends Component<any, NavbarState> {
                     to={`/u/${UserService.Instance.user.username}`}
                   >
                     <span>
-                      {UserService.Instance.user.avatar && (
+                      {UserService.Instance.user.avatar && showAvatars() && (
                         <img
                           src={pictshareAvatarThumbnail(
                             UserService.Instance.user.avatar
index 1f3a74ac7d9b58e4eb358f6fdb12fb5b6aad4453..8b4c1cb684511c5464cc6734f5fb4432d698c62b 100644 (file)
@@ -26,6 +26,7 @@ import {
   isVideo,
   getUnixTime,
   pictshareAvatarThumbnail,
+  showAvatars,
 } from '../utils';
 import { i18n } from '../i18next';
 import { T } from 'inferno-i18next';
@@ -249,7 +250,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
             <li className="list-inline-item">
               <span>{i18n.t('by')} </span>
               <Link className="text-info" to={`/u/${post.creator_name}`}>
-                {post.creator_avatar && (
+                {post.creator_avatar && showAvatars() && (
                   <img
                     height="32"
                     width="32"
index d92c06e1cbc5e4e5b0a5d10b8bd1b849d1d5e946..00c670d821d9361c206dfe747ef257c00bbfe429 100644 (file)
@@ -20,6 +20,7 @@ import {
   routeSearchTypeToEnum,
   routeSortTypeToEnum,
   pictshareAvatarThumbnail,
+  showAvatars,
 } from '../utils';
 import { PostListing } from './post-listing';
 import { SortSelect } from './sort-select';
@@ -288,7 +289,7 @@ export class Search extends Component<any, SearchState> {
                     className="text-info"
                     to={`/u/${(i.data as UserView).name}`}
                   >
-                    {(i.data as UserView).avatar && (
+                    {(i.data as UserView).avatar && showAvatars() && (
                       <img
                         height="32"
                         width="32"
index 0e95666fc9903bec2c9ded2bfb6fdc4ed2d7bcba..089dd5581f96d6ca3486f08058f5a87add7ebc8e 100644 (file)
@@ -8,7 +8,12 @@ import {
   UserView,
 } from '../interfaces';
 import { WebSocketService, UserService } from '../services';
-import { mdToHtml, getUnixTime, pictshareAvatarThumbnail } from '../utils';
+import {
+  mdToHtml,
+  getUnixTime,
+  pictshareAvatarThumbnail,
+  showAvatars,
+} from '../utils';
 import { CommunityForm } from './community-form';
 import { i18n } from '../i18next';
 import { T } from 'inferno-i18next';
@@ -194,7 +199,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
               {this.props.moderators.map(mod => (
                 <li class="list-inline-item">
                   <Link class="text-info" to={`/u/${mod.user_name}`}>
-                    {mod.avatar && (
+                    {mod.avatar && showAvatars() && (
                       <img
                         height="32"
                         width="32"
index 99c340c5e0a1cae406924efc1cf362b0f0052d2e..30ee3dcf1e26251becf5950601e7f29789c4ed2a 100644 (file)
@@ -28,6 +28,7 @@ import {
   themes,
   setTheme,
   languages,
+  showAvatars,
 } from '../utils';
 import { PostListing } from './post-listing';
 import { SortSelect } from './sort-select';
@@ -80,6 +81,8 @@ export class User extends Component<any, UserState> {
       comment_score: null,
       banned: null,
       avatar: null,
+      show_avatars: null,
+      send_notifications_to_email: null,
     },
     user_id: null,
     username: null,
@@ -99,6 +102,8 @@ export class User extends Component<any, UserState> {
       default_sort_type: null,
       default_listing_type: null,
       lang: null,
+      show_avatars: null,
+      send_notifications_to_email: null,
       auth: null,
     },
     userSettingsLoading: null,
@@ -207,7 +212,7 @@ export class User extends Component<any, UserState> {
           <div class="row">
             <div class="col-12 col-md-8">
               <h5>
-                {this.state.user.avatar && (
+                {this.state.user.avatar && showAvatars() && (
                   <img
                     height="80"
                     width="80"
@@ -610,6 +615,41 @@ export class User extends Component<any, UserState> {
                   </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">
                 <button type="submit" class="btn btn-block btn-secondary mr-4">
                   {this.state.userSettingsLoading ? (
@@ -804,6 +844,17 @@ export class User extends Component<any, UserState> {
     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) {
     i.state.userSettingsForm.theme = 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.avatar = UserService.Instance.user.avatar;
         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}`;
       window.scrollTo(0, 0);
index 1762bd60891cd0b140ef095649653b264e618307..7fc7a252901ac26bd73fe3b4269944366e091610 100644 (file)
@@ -81,6 +81,7 @@ export interface User {
   default_listing_type: ListingType;
   lang: string;
   avatar?: string;
+  show_avatars: boolean;
 }
 
 export interface UserView {
@@ -95,6 +96,8 @@ export interface UserView {
   number_of_comments: number;
   comment_score: number;
   banned: boolean;
+  show_avatars: boolean;
+  send_notifications_to_email: boolean;
 }
 
 export interface CommunityUser {
@@ -486,6 +489,8 @@ export interface UserSettingsForm {
   new_password?: string;
   new_password_verify?: string;
   old_password?: string;
+  show_avatars: boolean;
+  send_notifications_to_email: boolean;
   auth: string;
 }
 
index 5e0adbaa6c6b826b8643a8aef990cbcfd3600757..bd7e39ccd85ba9af65f8396fa94f879b17399798 100644 (file)
@@ -29,6 +29,7 @@ export const en = {
     preview: 'Preview',
     upload_image: 'upload image',
     avatar: 'Avatar',
+    show_avatars: 'Show Avatars',
     formatting_help: 'formatting help',
     view_source: 'view source',
     unlock: 'unlock',
@@ -126,6 +127,7 @@ export const en = {
     new_password: 'New Password',
     no_email_setup: "This server hasn't correctly set up email.",
     email: 'Email',
+    send_notifications_to_email: 'Send notifications to Email',
     optional: 'Optional',
     expires: 'Expires',
     language: 'Language',
index 29aabdcfe909bf5204468b1f667654d1f668b882..b9da6cb6467b721e210864bfb290da0277604bcb 100644 (file)
@@ -345,3 +345,10 @@ export function pictshareAvatarThumbnail(src: string): string {
   let out = `${split[0]}pictshare/96x96${split[1]}`;
   return out;
 }
+
+export function showAvatars(): boolean {
+  return (
+    (UserService.Instance.user && UserService.Instance.user.show_avatars) ||
+    !UserService.Instance.user
+  );
+}