ansible/inventory
ansible/passwords/
+docker/lemmy_mine.hjson
build/
.idea/
--- /dev/null
+-- 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;
--- /dev/null
+-- 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;
use super::*;
+use crate::send_email;
+use crate::settings::Settings;
#[derive(Serialize, Deserialize)]
pub struct 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() {
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,
};
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,
new_password: Option<String>,
new_password_verify: Option<String>,
old_password: Option<String>,
+ show_avatars: bool,
+ send_notifications_to_email: bool,
auth: String,
}
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
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) {
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) {
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) {
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();
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();
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();
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();
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();
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();
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();
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();
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();
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)]
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_ {
pub default_listing_type: i16,
pub lang: String,
pub avatar: Option<String>,
+ pub show_avatars: bool,
}
impl Claims {
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(),
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();
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();
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();
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();
fedi_name -> Varchar,
admin -> Bool,
banned -> Bool,
+ show_avatars -> Bool,
+ send_notifications_to_email -> Bool,
published -> Timestamp,
number_of_posts -> BigInt,
post_score -> BigInt,
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,
default_sort_type -> Int2,
default_listing_type -> Int2,
lang -> Varchar,
+ show_avatars -> Bool,
+ send_notifications_to_email -> Bool,
}
}
canMod,
isMod,
pictshareAvatarThumbnail,
+ showAvatars,
} from '../utils';
import * as moment from 'moment';
import { MomentTime } from './moment-time';
className="text-info"
to={`/u/${node.comment.creator_name}`}
>
- {node.comment.creator_avatar && (
+ {node.comment.creator_avatar && showAvatars() && (
<img
height="32"
width="32"
routeListingTypeToEnum,
postRefetchSeconds,
pictshareAvatarThumbnail,
+ showAvatars,
} from '../utils';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
{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"
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';
to={`/u/${UserService.Instance.user.username}`}
>
<span>
- {UserService.Instance.user.avatar && (
+ {UserService.Instance.user.avatar && showAvatars() && (
<img
src={pictshareAvatarThumbnail(
UserService.Instance.user.avatar
isVideo,
getUnixTime,
pictshareAvatarThumbnail,
+ showAvatars,
} from '../utils';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
<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"
routeSearchTypeToEnum,
routeSortTypeToEnum,
pictshareAvatarThumbnail,
+ showAvatars,
} from '../utils';
import { PostListing } from './post-listing';
import { SortSelect } from './sort-select';
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"
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';
{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"
themes,
setTheme,
languages,
+ showAvatars,
} from '../utils';
import { PostListing } from './post-listing';
import { SortSelect } from './sort-select';
comment_score: null,
banned: null,
avatar: null,
+ show_avatars: null,
+ send_notifications_to_email: null,
},
user_id: null,
username: null,
default_sort_type: null,
default_listing_type: null,
lang: null,
+ show_avatars: null,
+ send_notifications_to_email: null,
auth: null,
},
userSettingsLoading: null,
<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"
</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 ? (
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);
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);
default_listing_type: ListingType;
lang: string;
avatar?: string;
+ show_avatars: boolean;
}
export interface UserView {
number_of_comments: number;
comment_score: number;
banned: boolean;
+ show_avatars: boolean;
+ send_notifications_to_email: boolean;
}
export interface CommunityUser {
new_password?: string;
new_password_verify?: string;
old_password?: string;
+ show_avatars: boolean;
+ send_notifications_to_email: boolean;
auth: string;
}
preview: 'Preview',
upload_image: 'upload image',
avatar: 'Avatar',
+ show_avatars: 'Show Avatars',
formatting_help: 'formatting help',
view_source: 'view source',
unlock: 'unlock',
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',
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
+ );
+}