]> Untitled Git - lemmy.git/commitdiff
Adding user avatars / icons. Requires pictshare.
authorDessalines <tyhou13@gmx.com>
Sun, 29 Dec 2019 20:39:48 +0000 (15:39 -0500)
committerDessalines <tyhou13@gmx.com>
Sun, 29 Dec 2019 20:39:48 +0000 (15:39 -0500)
- Fixes #188

32 files changed:
README.md
server/migrations/2019-12-29-164820_add_avatar/down.sql [new file with mode: 0644]
server/migrations/2019-12-29-164820_add_avatar/up.sql [new file with mode: 0644]
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/community_view.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/site_view.rs
server/src/db/user.rs
server/src/db/user_mention.rs
server/src/db/user_mention_view.rs
server/src/db/user_view.rs
server/src/schema.rs
ui/.eslintignore [new file with mode: 0644]
ui/fuse.js
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
ui/translation_report.ts

index e3f85eb62134b4c6ac35a07e1e5e3d2e02800df1..5407ac0bf60d4d8e34cbaff87e21b29fb3b471c4 100644 (file)
--- a/README.md
+++ b/README.md
@@ -257,15 +257,15 @@ If you'd like to add translations, take a look a look at the [English translatio
 
 lang | done | missing
 --- | --- | ---
-de | 100% |  
-eo | 86% | number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,theme,are_you_sure,yes,no 
-es | 95% | archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default 
-fr | 95% | archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default 
-it | 96% | archive_link,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default 
-nl | 88% | preview,upload_image,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,theme 
-ru | 82% | cross_posts,cross_post,number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no 
-sv | 95% | archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default 
-zh | 80% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no 
+de | 97% | avatar,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
+eo | 84% | number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,are_you_sure,yes,no 
+es | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
+fr | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
+it | 93% | avatar,archive_link,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
+nl | 86% | preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme 
+ru | 80% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no 
+sv | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
+zh | 78% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no 
 
 
 If you'd like to update this report, run:
diff --git a/server/migrations/2019-12-29-164820_add_avatar/down.sql b/server/migrations/2019-12-29-164820_add_avatar/down.sql
new file mode 100644 (file)
index 0000000..74b4146
--- /dev/null
@@ -0,0 +1,224 @@
+-- the views
+drop view user_mention_view;
+drop view reply_view;
+drop view comment_view;
+drop view user_view;
+
+-- user
+create view user_view as 
+select id,
+name,
+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;
+
+-- post
+-- Recreate the view
+drop view post_view;
+create view post_view as
+with all_post as
+(
+  select        
+  p.*,
+  (select u.banned from user_ u where p.creator_id = u.id) as banned,
+  (select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community,
+  (select name from user_ where p.creator_id = user_.id) as creator_name,
+  (select name from community where p.community_id = community.id) as community_name,
+  (select removed from community c where p.community_id = c.id) as community_removed,
+  (select deleted from community c where p.community_id = c.id) as community_deleted,
+  (select nsfw from community c where p.community_id = c.id) as community_nsfw,
+  (select count(*) from comment where comment.post_id = p.id) as number_of_comments,
+  coalesce(sum(pl.score), 0) as score,
+  count (case when pl.score = 1 then 1 else null end) as upvotes,
+  count (case when pl.score = -1 then 1 else null end) as downvotes,
+  hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank
+  from post p
+  left join post_like pl on p.id = pl.post_id
+  group by p.id
+)
+
+select
+ap.*,
+u.id as user_id,
+coalesce(pl.score, 0) as my_vote,
+(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
+(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read,
+(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved
+from user_ u
+cross join all_post ap
+left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
+
+union all
+
+select 
+ap.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from all_post ap
+;
+
+-- community
+
+drop view community_view;
+create view community_view as 
+with all_community as
+(
+  select *,
+  (select name from user_ u where c.creator_id = u.id) as creator_name,
+  (select name from category ct where c.category_id = ct.id) as category_name,
+  (select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers,
+  (select count(*) from post p where p.community_id = c.id) as number_of_posts,
+  (select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments,
+  hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank
+  from community c
+)
+
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join all_community ac
+
+union all
+
+select 
+ac.*,
+null as user_id,
+null as subscribed
+from all_community ac
+;
+
+-- Reply and comment view
+create view comment_view as
+with all_comment as
+(
+  select        
+  c.*,
+  (select community_id from post p where p.id = c.post_id),
+  (select u.banned from user_ u where c.creator_id = u.id) as banned,
+  (select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community,
+  (select name from user_ where c.creator_id = user_.id) as creator_name,
+  coalesce(sum(cl.score), 0) as score,
+  count (case when cl.score = 1 then 1 else null end) as upvotes,
+  count (case when cl.score = -1 then 1 else null end) as downvotes
+  from comment c
+  left join comment_like cl on c.id = cl.comment_id
+  group by c.id
+)
+
+select
+ac.*,
+u.id as user_id,
+coalesce(cl.score, 0) as my_vote,
+(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+
+union all
+
+select 
+    ac.*,
+    null as user_id, 
+    null as my_vote,
+    null as saved
+from all_comment ac
+;
+
+create view reply_view as 
+with closereply as (
+    select 
+    c2.id, 
+    c2.creator_id as sender_id, 
+    c.creator_id as recipient_id
+    from comment c
+    inner join comment c2 on c.id = c2.parent_id
+    where c2.creator_id != c.creator_id
+    -- Do union where post is null
+    union
+    select
+    c.id,
+    c.creator_id as sender_id,
+    p.creator_id as recipient_id
+    from comment c, post p
+    where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
+)
+select cv.*,
+closereply.recipient_id
+from comment_view cv, closereply
+where closereply.id = cv.id
+;
+
+-- user mention
+create view user_mention_view as
+select 
+    c.id,
+    um.id as user_mention_id,
+    c.creator_id,
+    c.post_id,
+    c.parent_id,
+    c.content,
+    c.removed,
+    um.read,
+    c.published,
+    c.updated,
+    c.deleted,
+    c.community_id,
+    c.banned,
+    c.banned_from_community,
+    c.creator_name,
+    c.score,
+    c.upvotes,
+    c.downvotes,
+    c.user_id,
+    c.my_vote,
+    c.saved,
+    um.recipient_id
+from user_mention um, comment_view c
+where um.comment_id = c.id;
+
+-- community tables
+drop view community_moderator_view;
+drop view community_follower_view;
+drop view community_user_ban_view;
+drop view site_view;
+
+create view community_moderator_view as 
+select *,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select name from community c where cm.community_id = c.id) as community_name
+from community_moderator cm;
+
+create view community_follower_view as 
+select *,
+(select name from user_ u where cf.user_id = u.id) as user_name,
+(select name from community c where cf.community_id = c.id) as community_name
+from community_follower cf;
+
+create view community_user_ban_view as 
+select *,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select name from community c where cm.community_id = c.id) as community_name
+from community_user_ban cm;
+
+create view site_view as 
+select *,
+(select name from user_ u where s.creator_id = u.id) as creator_name,
+(select count(*) from user_) as number_of_users,
+(select count(*) from post) as number_of_posts,
+(select count(*) from comment) as number_of_comments,
+(select count(*) from community) as number_of_communities
+from site s;
+
+alter table user_ rename column avatar to icon;
+alter table user_ alter column icon type bytea using icon::bytea;
diff --git a/server/migrations/2019-12-29-164820_add_avatar/up.sql b/server/migrations/2019-12-29-164820_add_avatar/up.sql
new file mode 100644 (file)
index 0000000..f926515
--- /dev/null
@@ -0,0 +1,234 @@
+-- Rename to avatar
+alter table user_ rename column icon to avatar;
+alter table user_ alter column avatar type text;
+
+-- Rebuild nearly all the views, to include the creator avatars
+
+-- user
+drop view user_view;
+create view user_view as 
+select id,
+name,
+avatar,
+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;
+
+-- post
+-- Recreate the view
+drop view post_view;
+create view post_view as
+with all_post as
+(
+  select        
+  p.*,
+  (select u.banned from user_ u where p.creator_id = u.id) as banned,
+  (select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community,
+  (select name from user_ where p.creator_id = user_.id) as creator_name,
+  (select avatar from user_ where p.creator_id = user_.id) as creator_avatar,
+  (select name from community where p.community_id = community.id) as community_name,
+  (select removed from community c where p.community_id = c.id) as community_removed,
+  (select deleted from community c where p.community_id = c.id) as community_deleted,
+  (select nsfw from community c where p.community_id = c.id) as community_nsfw,
+  (select count(*) from comment where comment.post_id = p.id) as number_of_comments,
+  coalesce(sum(pl.score), 0) as score,
+  count (case when pl.score = 1 then 1 else null end) as upvotes,
+  count (case when pl.score = -1 then 1 else null end) as downvotes,
+  hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank
+  from post p
+  left join post_like pl on p.id = pl.post_id
+  group by p.id
+)
+
+select
+ap.*,
+u.id as user_id,
+coalesce(pl.score, 0) as my_vote,
+(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
+(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read,
+(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved
+from user_ u
+cross join all_post ap
+left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
+
+union all
+
+select 
+ap.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from all_post ap
+;
+
+
+-- community
+drop view community_view;
+create view community_view as 
+with all_community as
+(
+  select *,
+  (select name from user_ u where c.creator_id = u.id) as creator_name,
+  (select avatar from user_ u where c.creator_id = u.id) as creator_avatar,
+  (select name from category ct where c.category_id = ct.id) as category_name,
+  (select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers,
+  (select count(*) from post p where p.community_id = c.id) as number_of_posts,
+  (select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments,
+  hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank
+  from community c
+)
+
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join all_community ac
+
+union all
+
+select 
+ac.*,
+null as user_id,
+null as subscribed
+from all_community ac
+;
+
+-- reply and comment view
+drop view reply_view;
+drop view user_mention_view;
+drop view comment_view;
+create view comment_view as
+with all_comment as
+(
+  select        
+  c.*,
+  (select community_id from post p where p.id = c.post_id),
+  (select u.banned from user_ u where c.creator_id = u.id) as banned,
+  (select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community,
+  (select name from user_ where c.creator_id = user_.id) as creator_name,
+  (select avatar from user_ where c.creator_id = user_.id) as creator_avatar,
+  coalesce(sum(cl.score), 0) as score,
+  count (case when cl.score = 1 then 1 else null end) as upvotes,
+  count (case when cl.score = -1 then 1 else null end) as downvotes
+  from comment c
+  left join comment_like cl on c.id = cl.comment_id
+  group by c.id
+)
+
+select
+ac.*,
+u.id as user_id,
+coalesce(cl.score, 0) as my_vote,
+(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+
+union all
+
+select 
+    ac.*,
+    null as user_id, 
+    null as my_vote,
+    null as saved
+from all_comment ac
+;
+
+create view reply_view as 
+with closereply as (
+    select 
+    c2.id, 
+    c2.creator_id as sender_id, 
+    c.creator_id as recipient_id
+    from comment c
+    inner join comment c2 on c.id = c2.parent_id
+    where c2.creator_id != c.creator_id
+    -- Do union where post is null
+    union
+    select
+    c.id,
+    c.creator_id as sender_id,
+    p.creator_id as recipient_id
+    from comment c, post p
+    where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
+)
+select cv.*,
+closereply.recipient_id
+from comment_view cv, closereply
+where closereply.id = cv.id
+;
+
+-- user mention
+create view user_mention_view as
+select 
+    c.id,
+    um.id as user_mention_id,
+    c.creator_id,
+    c.post_id,
+    c.parent_id,
+    c.content,
+    c.removed,
+    um.read,
+    c.published,
+    c.updated,
+    c.deleted,
+    c.community_id,
+    c.banned,
+    c.banned_from_community,
+    c.creator_name,
+    c.creator_avatar,
+    c.score,
+    c.upvotes,
+    c.downvotes,
+    c.user_id,
+    c.my_vote,
+    c.saved,
+    um.recipient_id
+from user_mention um, comment_view c
+where um.comment_id = c.id;
+
+-- community views
+drop view community_moderator_view;
+drop view community_follower_view;
+drop view community_user_ban_view;
+drop view site_view;
+
+create view community_moderator_view as 
+select *,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select avatar from user_ u where cm.user_id = u.id),
+(select name from community c where cm.community_id = c.id) as community_name
+from community_moderator cm;
+
+create view community_follower_view as 
+select *,
+(select name from user_ u where cf.user_id = u.id) as user_name,
+(select avatar from user_ u where cf.user_id = u.id),
+(select name from community c where cf.community_id = c.id) as community_name
+from community_follower cf;
+
+create view community_user_ban_view as 
+select *,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select avatar from user_ u where cm.user_id = u.id),
+(select name from community c where cm.community_id = c.id) as community_name
+from community_user_ban cm;
+
+create view site_view as 
+select *,
+(select name from user_ u where s.creator_id = u.id) as creator_name,
+(select avatar from user_ u where s.creator_id = u.id) as creator_avatar,
+(select count(*) from user_) as number_of_users,
+(select count(*) from post) as number_of_posts,
+(select count(*) from comment) as number_of_comments,
+(select count(*) from community) as number_of_communities
+from site s;
index a04ba4b239168b7c7a3c6cf54e2ab3fd5630f6ab..e8ad20aa41dafc63f7ef029f6524484003fa57a0 100644 (file)
@@ -27,6 +27,7 @@ pub struct SaveUserSettings {
   default_sort_type: i16,
   default_listing_type: i16,
   lang: String,
+  avatar: Option<String>,
   auth: String,
 }
 
@@ -220,6 +221,7 @@ impl Perform<LoginResponse> for Oper<Register> {
       name: data.username.to_owned(),
       fedi_name: Settings::get().hostname.to_owned(),
       email: data.email.to_owned(),
+      avatar: None,
       password_encrypted: data.password.to_owned(),
       preferred_username: None,
       updated: None,
@@ -314,6 +316,7 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
       name: read_user.name,
       fedi_name: read_user.fedi_name,
       email: read_user.email,
+      avatar: data.avatar.to_owned(),
       password_encrypted: read_user.password_encrypted,
       preferred_username: read_user.preferred_username,
       updated: Some(naive_now()),
@@ -372,7 +375,12 @@ impl Perform<GetUserDetailsResponse> for Oper<GetUserDetails> {
           data.username.to_owned().unwrap_or("admin".to_string()),
         ) {
           Ok(user) => user.id,
-          Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_that_username_or_email"))?
+          Err(_e) => {
+            return Err(APIError::err(
+              &self.op,
+              "couldnt_find_that_username_or_email",
+            ))?
+          }
         }
       }
     };
@@ -449,6 +457,7 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
       name: read_user.name,
       fedi_name: read_user.fedi_name,
       email: read_user.email,
+      avatar: read_user.avatar,
       password_encrypted: read_user.password_encrypted,
       preferred_username: read_user.preferred_username,
       updated: Some(naive_now()),
@@ -511,6 +520,7 @@ impl Perform<BanUserResponse> for Oper<BanUser> {
       name: read_user.name,
       fedi_name: read_user.fedi_name,
       email: read_user.email,
+      avatar: read_user.avatar,
       password_encrypted: read_user.password_encrypted,
       preferred_username: read_user.preferred_username,
       updated: Some(naive_now()),
@@ -848,6 +858,7 @@ impl Perform<LoginResponse> for Oper<PasswordChange> {
       name: read_user.name,
       fedi_name: read_user.fedi_name,
       email: read_user.email,
+      avatar: read_user.avatar,
       password_encrypted: data.password.to_owned(),
       preferred_username: read_user.preferred_username,
       updated: Some(naive_now()),
index 9b861f45f2a8014600019ef55bf9db2c855f58f5..b1a01b6d530ebbd3c176147b672bcb3cd565e88e 100644 (file)
@@ -22,7 +22,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "here".into(),
       email: None,
-      icon: None,
+      avatar: None,
       published: naive_now(),
       admin: false,
       banned: false,
index b7bd562d2e49485a70bb024475e0e8de40572279..6419535619c6da791419622e5f268b029b08c4eb 100644 (file)
@@ -174,6 +174,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
+      avatar: None,
       admin: false,
       banned: false,
       updated: None,
index 5c321e2e2776c661240ba4d9ffaca8a3f00242ba..c56da51da973059dec1fcb3f83933aab16922d98 100644 (file)
@@ -18,6 +18,7 @@ table! {
     banned -> Bool,
     banned_from_community -> Bool,
     creator_name -> Varchar,
+    creator_avatar -> Nullable<Text>,
     score -> BigInt,
     upvotes -> BigInt,
     downvotes -> BigInt,
@@ -46,6 +47,7 @@ pub struct CommentView {
   pub banned: bool,
   pub banned_from_community: bool,
   pub creator_name: String,
+  pub creator_avatar: Option<String>,
   pub score: i64,
   pub upvotes: i64,
   pub downvotes: i64,
@@ -226,6 +228,7 @@ table! {
     banned -> Bool,
     banned_from_community -> Bool,
     creator_name -> Varchar,
+    creator_avatar -> Nullable<Text>,
     score -> BigInt,
     upvotes -> BigInt,
     downvotes -> BigInt,
@@ -255,6 +258,7 @@ pub struct ReplyView {
   pub banned: bool,
   pub banned_from_community: bool,
   pub creator_name: String,
+  pub creator_avatar: Option<String>,
   pub score: i64,
   pub upvotes: i64,
   pub downvotes: i64,
@@ -368,6 +372,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
+      avatar: None,
       admin: false,
       banned: false,
       updated: None,
@@ -447,6 +452,7 @@ mod tests {
       published: inserted_comment.published,
       updated: None,
       creator_name: inserted_user.name.to_owned(),
+      creator_avatar: None,
       score: 1,
       downvotes: 0,
       upvotes: 1,
@@ -470,6 +476,7 @@ mod tests {
       published: inserted_comment.published,
       updated: None,
       creator_name: inserted_user.name.to_owned(),
+      creator_avatar: None,
       score: 1,
       downvotes: 0,
       upvotes: 1,
index e8bf5bbc4e6af6182c84280b1f0cab0bd54e46cd..b5d0538443f979250f2a33a018e5e88abf3a7da9 100644 (file)
@@ -220,6 +220,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
+      avatar: None,
       admin: false,
       banned: false,
       updated: None,
index 157c4d91297b504e97450de9ebd9ed78496cb723..e57fd759c4cb44d75a52713b2d914a18b6815aa4 100644 (file)
@@ -16,6 +16,7 @@ table! {
     deleted -> Bool,
     nsfw -> Bool,
     creator_name -> Varchar,
+    creator_avatar -> Nullable<Text>,
     category_name -> Varchar,
     number_of_subscribers -> BigInt,
     number_of_posts -> BigInt,
@@ -33,6 +34,7 @@ table! {
     user_id -> Int4,
     published -> Timestamp,
     user_name -> Varchar,
+    avatar -> Nullable<Text>,
     community_name -> Varchar,
   }
 }
@@ -44,6 +46,7 @@ table! {
     user_id -> Int4,
     published -> Timestamp,
     user_name -> Varchar,
+    avatar -> Nullable<Text>,
     community_name -> Varchar,
   }
 }
@@ -55,6 +58,7 @@ table! {
     user_id -> Int4,
     published -> Timestamp,
     user_name -> Varchar,
+    avatar -> Nullable<Text>,
     community_name -> Varchar,
   }
 }
@@ -76,6 +80,7 @@ pub struct CommunityView {
   pub deleted: bool,
   pub nsfw: bool,
   pub creator_name: String,
+  pub creator_avatar: Option<String>,
   pub category_name: String,
   pub number_of_subscribers: i64,
   pub number_of_posts: i64,
@@ -224,6 +229,7 @@ pub struct CommunityModeratorView {
   pub user_id: i32,
   pub published: chrono::NaiveDateTime,
   pub user_name: String,
+  pub avatar: Option<String>,
   pub community_name: String,
 }
 
@@ -253,6 +259,7 @@ pub struct CommunityFollowerView {
   pub user_id: i32,
   pub published: chrono::NaiveDateTime,
   pub user_name: String,
+  pub avatar: Option<String>,
   pub community_name: String,
 }
 
@@ -282,6 +289,7 @@ pub struct CommunityUserBanView {
   pub user_id: i32,
   pub published: chrono::NaiveDateTime,
   pub user_name: String,
+  pub avatar: Option<String>,
   pub community_name: String,
 }
 
index 22547ca47164d1b9925e4191b1bdae3c6ee2a24a..7f1c3499c1b4f3653d29c67efca1803ce3250dbf 100644 (file)
@@ -442,6 +442,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
+      avatar: None,
       admin: false,
       banned: false,
       updated: None,
@@ -460,6 +461,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
+      avatar: None,
       admin: false,
       banned: false,
       updated: None,
index 91e27c57aafcc26b989e38b62f43aa666d417c69..b7983f535e148e40937828de79f6d2c500536320 100644 (file)
@@ -92,6 +92,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
+      avatar: None,
       admin: false,
       banned: false,
       updated: None,
index 96ae31db09756b42d91528fd5be07cde457627bb..da669ea13423c64b9f4e43c6a830613a62ca43dc 100644 (file)
@@ -187,6 +187,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
+      avatar: None,
       admin: false,
       banned: false,
       updated: None,
index 615b9b0db993d133e236b1eab283f1d15e344bd8..d05eeccad7f5fd9690adbefca7e3af417a8921f7 100644 (file)
@@ -21,6 +21,7 @@ table! {
     banned_from_community -> Bool,
     stickied -> Bool,
     creator_name -> Varchar,
+    creator_avatar -> Nullable<Text>,
     community_name -> Varchar,
     community_removed -> Bool,
     community_deleted -> Bool,
@@ -59,6 +60,7 @@ pub struct PostView {
   pub banned_from_community: bool,
   pub stickied: bool,
   pub creator_name: String,
+  pub creator_avatar: Option<String>,
   pub community_name: String,
   pub community_removed: bool,
   pub community_deleted: bool,
@@ -303,6 +305,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
+      avatar: None,
       updated: None,
       admin: false,
       banned: false,
@@ -377,6 +380,7 @@ mod tests {
       body: None,
       creator_id: inserted_user.id,
       creator_name: user_name.to_owned(),
+      creator_avatar: None,
       banned: false,
       banned_from_community: false,
       community_id: inserted_community.id,
@@ -414,6 +418,7 @@ mod tests {
       stickied: false,
       creator_id: inserted_user.id,
       creator_name: user_name.to_owned(),
+      creator_avatar: None,
       banned: false,
       banned_from_community: false,
       community_id: inserted_community.id,
index 40b1265f5f96926287ee6d6a97b786db8c4c5767..674a7a6e75d724092bfdd75e759a3acb3d1c1268 100644 (file)
@@ -12,6 +12,7 @@ table! {
     open_registration -> Bool,
     enable_nsfw -> Bool,
     creator_name -> Varchar,
+    creator_avatar -> Nullable<Text>,
     number_of_users -> BigInt,
     number_of_posts -> BigInt,
     number_of_comments -> BigInt,
@@ -34,6 +35,7 @@ pub struct SiteView {
   pub open_registration: bool,
   pub enable_nsfw: bool,
   pub creator_name: String,
+  pub creator_avatar: Option<String>,
   pub number_of_users: i64,
   pub number_of_posts: i64,
   pub number_of_comments: i64,
index c636f4e673e9e64003b623bad21b8c1a0d475db6..db4aa453c778cebe331a0305ba6534e3a5ca0cd1 100644 (file)
@@ -14,7 +14,7 @@ pub struct User_ {
   pub preferred_username: Option<String>,
   pub password_encrypted: String,
   pub email: Option<String>,
-  pub icon: Option<Vec<u8>>,
+  pub avatar: Option<String>,
   pub admin: bool,
   pub banned: bool,
   pub published: chrono::NaiveDateTime,
@@ -36,6 +36,7 @@ pub struct UserForm {
   pub admin: bool,
   pub banned: bool,
   pub email: Option<String>,
+  pub avatar: Option<String>,
   pub updated: Option<chrono::NaiveDateTime>,
   pub show_nsfw: bool,
   pub theme: String,
@@ -99,6 +100,7 @@ pub struct Claims {
   pub default_sort_type: i16,
   pub default_listing_type: i16,
   pub lang: String,
+  pub avatar: Option<String>,
 }
 
 impl Claims {
@@ -123,6 +125,7 @@ impl User_ {
       default_sort_type: self.default_sort_type,
       default_listing_type: self.default_listing_type,
       lang: self.lang.to_owned(),
+      avatar: self.avatar.to_owned(),
     };
     encode(
       &Header::default(),
@@ -176,6 +179,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
+      avatar: None,
       admin: false,
       banned: false,
       updated: None,
@@ -195,7 +199,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
-      icon: None,
+      avatar: None,
       admin: false,
       banned: false,
       published: inserted_user.published,
index 7eb4d486a3f6ee0879f32e23f7532819763ccfcf..5392c87a23083990742aa99662d91c776ee7f8de 100644 (file)
@@ -68,6 +68,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
+      avatar: None,
       admin: false,
       banned: false,
       updated: None,
@@ -86,6 +87,7 @@ mod tests {
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
+      avatar: None,
       admin: false,
       banned: false,
       updated: None,
index 45541861e7aea0fa6c97d3a0ca665655febf29f7..7a45d22204be6bd6d6a03476c3d8d4d18e5b936d 100644 (file)
@@ -20,6 +20,7 @@ table! {
     banned -> Bool,
     banned_from_community -> Bool,
     creator_name -> Varchar,
+    creator_avatar -> Nullable<Text>,
     score -> BigInt,
     upvotes -> BigInt,
     downvotes -> BigInt,
@@ -50,6 +51,7 @@ pub struct UserMentionView {
   pub banned: bool,
   pub banned_from_community: bool,
   pub creator_name: String,
+  pub creator_avatar: Option<String>,
   pub score: i64,
   pub upvotes: i64,
   pub downvotes: i64,
@@ -78,7 +80,7 @@ impl<'a> UserMentionQueryBuilder<'a> {
     UserMentionQueryBuilder {
       conn,
       query,
-      for_user_id: for_user_id,
+      for_user_id,
       sort: &SortType::New,
       unread_only: false,
       page: None,
index 0ed95eefd45e97671b88173867004aea20b3cd9d..616159de5e1e3b85a22bfcc0de3348a3f0b90202 100644 (file)
@@ -6,6 +6,7 @@ table! {
   user_view (id) {
     id -> Int4,
     name -> Varchar,
+    avatar -> Nullable<Text>,
     fedi_name -> Varchar,
     admin -> Bool,
     banned -> Bool,
@@ -24,6 +25,7 @@ table! {
 pub struct UserView {
   pub id: i32,
   pub name: String,
+  pub avatar: Option<String>,
   pub fedi_name: String,
   pub admin: bool,
   pub banned: bool,
index 118f5f4a616a07b807d8b1cd291e32c61cc897d8..86834072c85a5bbd4203f3d6e595200394c16014 100644 (file)
@@ -260,7 +260,7 @@ table! {
         preferred_username -> Nullable<Varchar>,
         password_encrypted -> Text,
         email -> Nullable<Text>,
-        icon -> Nullable<Bytea>,
+        avatar -> Nullable<Text>,
         admin -> Bool,
         banned -> Bool,
         published -> Timestamp,
diff --git a/ui/.eslintignore b/ui/.eslintignore
new file mode 100644 (file)
index 0000000..aa6ed17
--- /dev/null
@@ -0,0 +1,2 @@
+fuse.js
+translation_report.ts
index 85eb75e2abf29752f6472664cd0383425ba8ead4..3feab49053ce246f47c46adf036f9bfcc7f3e0a5 100644 (file)
@@ -1,11 +1,11 @@
-const {
+import {
   FuseBox,
   Sparky,
   EnvPlugin,
   CSSPlugin,
   WebIndexPlugin,
-  QuantumPlugin
-} = require('fuse-box');
+  QuantumPlugin,
+} from 'fuse-box';
 // const transformInferno = require('../../dist').default
 const transformInferno = require('ts-transform-inferno').default;
 const transformClasscat = require('ts-transform-classcat').default;
@@ -25,22 +25,22 @@ Sparky.task('config', _ => {
       before: [transformClasscat(), transformInferno()],
     },
     alias: {
-      'locale': 'moment/locale'
-               },
+      locale: 'moment/locale',
+    },
     plugins: [
       EnvPlugin({ NODE_ENV: isProduction ? 'production' : 'development' }),
       CSSPlugin(),
       WebIndexPlugin({
         title: 'Inferno Typescript FuseBox Example',
         template: 'src/index.html',
-        path: isProduction ? "/static" : "/"
+        path: isProduction ? '/static' : '/',
       }),
       isProduction &&
-      QuantumPlugin({
-        bakeApiIntoBundle: 'app',
-        treeshake: true,
-        uglify: true,
-      }),
+        QuantumPlugin({
+          bakeApiIntoBundle: 'app',
+          treeshake: true,
+          uglify: true,
+        }),
     ],
   });
   app = fuse.bundle('app').instructions('>index.tsx');
@@ -48,7 +48,9 @@ Sparky.task('config', _ => {
 // Sparky.task('version', _ => setVersion());
 Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/'));
 Sparky.task('env', _ => (isProduction = true));
-Sparky.task('copy-assets', () => Sparky.src('assets/**/**.*').dest(isProduction ? 'dist/' : 'dist/static'));
+Sparky.task('copy-assets', () =>
+  Sparky.src('assets/**/**.*').dest(isProduction ? 'dist/' : 'dist/static')
+);
 Sparky.task('dev', ['clean', 'config', 'copy-assets'], _ => {
   fuse.dev();
   app.hmr().watch();
index f408ef2766c89acfbdc1e964947aff5f4ac2c153..34ec0dfbbf0f0e0cdeb549588ca5a4f1ac32af6e 100644 (file)
@@ -17,7 +17,13 @@ import {
   BanType,
 } from '../interfaces';
 import { WebSocketService, UserService } from '../services';
-import { mdToHtml, getUnixTime, canMod, isMod } from '../utils';
+import {
+  mdToHtml,
+  getUnixTime,
+  canMod,
+  isMod,
+  pictshareAvatarThumbnail,
+} from '../utils';
 import * as moment from 'moment';
 import { MomentTime } from './moment-time';
 import { CommentForm } from './comment-form';
@@ -128,7 +134,15 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                 className="text-info"
                 to={`/u/${node.comment.creator_name}`}
               >
-                {node.comment.creator_name}
+                {node.comment.creator_avatar && (
+                  <img
+                    height="32"
+                    width="32"
+                    src={pictshareAvatarThumbnail(node.comment.creator_avatar)}
+                    class="rounded-circle mr-1"
+                  />
+                )}
+                <span>{node.comment.creator_name}</span>
               </Link>
             </li>
             {this.isMod && (
index 0d6be91dc9319d6dad5e8bd1a71754db203c1c6c..d1042f384b6b2aa6084f3d2df0684225aaeadf3d 100644 (file)
@@ -31,6 +31,7 @@ import {
   routeSortTypeToEnum,
   routeListingTypeToEnum,
   postRefetchSeconds,
+  pictshareAvatarThumbnail,
 } from '../utils';
 import { i18n } from '../i18next';
 import { T } from 'inferno-i18next';
@@ -65,6 +66,9 @@ export class Main extends Component<any, MainState> {
         number_of_posts: null,
         number_of_comments: null,
         number_of_communities: null,
+        enable_downvotes: null,
+        open_registration: null,
+        enable_nsfw: null,
       },
       admins: [],
       banned: [],
@@ -341,7 +345,15 @@ 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.name}
+                    {admin.avatar && (
+                      <img
+                        height="32"
+                        width="32"
+                        src={pictshareAvatarThumbnail(admin.avatar)}
+                        class="rounded-circle mr-1"
+                      />
+                    )}
+                    <span>{admin.name}</span>
                   </Link>
                 </li>
               ))}
index 306dc74fb6a3fb6bac20fcdbc7f4e7a9ac657ebd..f1a989419da9f7d3f4f7347cc20772b078645041 100644 (file)
@@ -13,7 +13,7 @@ import {
   GetSiteResponse,
   Comment,
 } from '../interfaces';
-import { msgOp } from '../utils';
+import { msgOp, pictshareAvatarThumbnail } from '../utils';
 import { version } from '../version';
 import { i18n } from '../i18next';
 import { T } from 'inferno-i18next';
@@ -151,7 +151,19 @@ export class Navbar extends Component<any, NavbarState> {
                     class="nav-link"
                     to={`/u/${UserService.Instance.user.username}`}
                   >
-                    {UserService.Instance.user.username}
+                    <span>
+                      {UserService.Instance.user.avatar && (
+                        <img
+                          src={pictshareAvatarThumbnail(
+                            UserService.Instance.user.avatar
+                          )}
+                          height="32"
+                          width="32"
+                          class="rounded-circle mr-2"
+                        />
+                      )}
+                      {UserService.Instance.user.username}
+                    </span>
                   </Link>
                 </li>
               </>
index 61a4c865c484a1082457f51656676f728ffc5e6f..1f3a74ac7d9b58e4eb358f6fdb12fb5b6aad4453 100644 (file)
@@ -25,6 +25,7 @@ import {
   isImage,
   isVideo,
   getUnixTime,
+  pictshareAvatarThumbnail,
 } from '../utils';
 import { i18n } from '../i18next';
 import { T } from 'inferno-i18next';
@@ -248,7 +249,15 @@ 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_name}
+                {post.creator_avatar && (
+                  <img
+                    height="32"
+                    width="32"
+                    src={pictshareAvatarThumbnail(post.creator_avatar)}
+                    class="rounded-circle mr-1"
+                  />
+                )}
+                <span>{post.creator_name}</span>
               </Link>
               {this.isMod && (
                 <span className="mx-1 badge badge-light">
index bba7c5adc73cd8c900e2fe4ff3ccde5211f81846..d92c06e1cbc5e4e5b0a5d10b8bd1b849d1d5e946 100644 (file)
@@ -19,6 +19,7 @@ import {
   fetchLimit,
   routeSearchTypeToEnum,
   routeSortTypeToEnum,
+  pictshareAvatarThumbnail,
 } from '../utils';
 import { PostListing } from './post-listing';
 import { SortSelect } from './sort-select';
@@ -286,7 +287,19 @@ export class Search extends Component<any, SearchState> {
                   <Link
                     className="text-info"
                     to={`/u/${(i.data as UserView).name}`}
-                  >{`/u/${(i.data as UserView).name}`}</Link>
+                  >
+                    {(i.data as UserView).avatar && (
+                      <img
+                        height="32"
+                        width="32"
+                        src={pictshareAvatarThumbnail(
+                          (i.data as UserView).avatar
+                        )}
+                        class="rounded-circle mr-1"
+                      />
+                    )}
+                    <span>{`/u/${(i.data as UserView).name}`}</span>
+                  </Link>
                 </span>
                 <span>{` - ${
                   (i.data as UserView).comment_score
index 85f81a30c0a31b2de2db9b2fac3c57f89cefe5ee..0e95666fc9903bec2c9ded2bfb6fdc4ed2d7bcba 100644 (file)
@@ -8,7 +8,7 @@ import {
   UserView,
 } from '../interfaces';
 import { WebSocketService, UserService } from '../services';
-import { mdToHtml, getUnixTime } from '../utils';
+import { mdToHtml, getUnixTime, pictshareAvatarThumbnail } from '../utils';
 import { CommunityForm } from './community-form';
 import { i18n } from '../i18next';
 import { T } from 'inferno-i18next';
@@ -194,7 +194,15 @@ 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.user_name}
+                    {mod.avatar && (
+                      <img
+                        height="32"
+                        width="32"
+                        src={pictshareAvatarThumbnail(mod.avatar)}
+                        class="rounded-circle mr-1"
+                      />
+                    )}
+                    <span>{mod.user_name}</span>
                   </Link>
                 </li>
               ))}
index 6d6a2e0cc571ee29d43c181288301f15bf3581a0..e97b26f90d3fd2f5bef59d9f2e7f728efc04dddf 100644 (file)
@@ -58,6 +58,7 @@ interface UserState {
   sort: SortType;
   page: number;
   loading: boolean;
+  avatarLoading: boolean;
   userSettingsForm: UserSettingsForm;
   userSettingsLoading: boolean;
   deleteAccountLoading: boolean;
@@ -78,6 +79,7 @@ export class User extends Component<any, UserState> {
       number_of_comments: null,
       comment_score: null,
       banned: null,
+      avatar: null,
     },
     user_id: null,
     username: null,
@@ -87,6 +89,7 @@ export class User extends Component<any, UserState> {
     posts: [],
     admins: [],
     loading: true,
+    avatarLoading: false,
     view: this.getViewFromProps(this.props),
     sort: this.getSortTypeFromProps(this.props),
     page: this.getPageFromProps(this.props),
@@ -96,6 +99,7 @@ export class User extends Component<any, UserState> {
       default_sort_type: null,
       default_listing_type: null,
       lang: null,
+      avatar: null,
       auth: null,
     },
     userSettingsLoading: null,
@@ -203,7 +207,17 @@ export class User extends Component<any, UserState> {
         ) : (
           <div class="row">
             <div class="col-12 col-md-8">
-              <h5>/u/{this.state.user.name}</h5>
+              <h5>
+                {this.state.user.avatar && (
+                  <img
+                    height="80"
+                    width="80"
+                    src={this.state.user.avatar}
+                    class="rounded-circle mr-2"
+                  />
+                )}
+                <span>/u/{this.state.user.name}</span>
+              </h5>
               {this.selects()}
               {this.state.view == View.Overview && this.overview()}
               {this.state.view == View.Comments && this.comments()}
@@ -422,6 +436,39 @@ export class User extends Component<any, UserState> {
               <T i18nKey="settings">#</T>
             </h5>
             <form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}>
+              <div class="form-group">
+                <div class="col-12">
+                  <label>
+                    <T i18nKey="avatar">#</T>
+                  </label>
+                  <form class="d-inline">
+                    <label
+                      htmlFor="file-upload"
+                      class="pointer ml-4 text-muted small font-weight-bold"
+                    >
+                      <img
+                        height="80"
+                        width="80"
+                        src={
+                          this.state.userSettingsForm.avatar
+                            ? this.state.userSettingsForm.avatar
+                            : 'https://via.placeholder.com/300/000?text=Avatar'
+                        }
+                        class="rounded-circle"
+                      />
+                    </label>
+                    <input
+                      id="file-upload"
+                      type="file"
+                      accept="image/*,video/*"
+                      name="file"
+                      class="d-none"
+                      disabled={!UserService.Instance.user}
+                      onChange={linkEvent(this, this.handleImageUpload)}
+                    />
+                  </form>
+                </div>
+              </div>
               <div class="form-group">
                 <div class="col-12">
                   <label>
@@ -739,6 +786,38 @@ export class User extends Component<any, UserState> {
     this.setState(this.state);
   }
 
+  handleImageUpload(i: User, event: any) {
+    event.preventDefault();
+    let file = event.target.files[0];
+    const imageUploadUrl = `/pictshare/api/upload.php`;
+    const formData = new FormData();
+    formData.append('file', file);
+
+    i.state.avatarLoading = true;
+    i.setState(i.state);
+
+    fetch(imageUploadUrl, {
+      method: 'POST',
+      body: formData,
+    })
+      .then(res => res.json())
+      .then(res => {
+        let url = `${window.location.origin}/pictshare/${res.url}`;
+        if (res.filetype == 'mp4') {
+          url += '/raw';
+        }
+        i.state.userSettingsForm.avatar = url;
+        console.log(url);
+        i.state.avatarLoading = false;
+        i.setState(i.state);
+      })
+      .catch(error => {
+        i.state.avatarLoading = false;
+        i.setState(i.state);
+        alert(error);
+      });
+  }
+
   handleUserSettingsSubmit(i: User, event: any) {
     event.preventDefault();
     i.state.userSettingsLoading = true;
@@ -802,6 +881,7 @@ export class User extends Component<any, UserState> {
         this.state.userSettingsForm.default_listing_type =
           UserService.Instance.user.default_listing_type;
         this.state.userSettingsForm.lang = UserService.Instance.user.lang;
+        this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
       }
       document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
       window.scrollTo(0, 0);
index 8b86088751f5718ffe91b4c156fe7fa1531f0431..7020ea492f1c079cdf43689212c6d8e404a645b8 100644 (file)
@@ -80,11 +80,13 @@ export interface User {
   default_sort_type: SortType;
   default_listing_type: ListingType;
   lang: string;
+  avatar?: string;
 }
 
 export interface UserView {
   id: number;
   name: string;
+  avatar?: string;
   fedi_name: string;
   published: string;
   number_of_posts: number;
@@ -98,6 +100,7 @@ export interface CommunityUser {
   id: number;
   user_id: number;
   user_name: string;
+  avatar?: string;
   community_id: number;
   community_name: string;
   published: string;
@@ -116,6 +119,7 @@ export interface Community {
   published: string;
   updated?: string;
   creator_name: string;
+  creator_avatar?: string;
   category_name: string;
   number_of_subscribers: number;
   number_of_posts: number;
@@ -141,6 +145,7 @@ export interface Post {
   published: string;
   updated?: string;
   creator_name: string;
+  creator_avatar?: string;
   community_name: string;
   community_removed: boolean;
   community_deleted: boolean;
@@ -172,6 +177,7 @@ export interface Comment {
   banned: boolean;
   banned_from_community: boolean;
   creator_name: string;
+  creator_avatar?: string;
   score: number;
   upvotes: number;
   downvotes: number;
@@ -474,6 +480,7 @@ export interface UserSettingsForm {
   default_sort_type: SortType;
   default_listing_type: ListingType;
   lang: string;
+  avatar?: string;
   auth: string;
 }
 
index 60a73a30d6b04bfe0163d22f9912460c837580ec..108620813bd1141994df9fe68328230d799d446c 100644 (file)
@@ -28,6 +28,7 @@ export const en = {
     cancel: 'Cancel',
     preview: 'Preview',
     upload_image: 'upload image',
+    avatar: 'Avatar',
     formatting_help: 'formatting help',
     view_source: 'view source',
     unlock: 'unlock',
index 98645fe390bedb927c85e76b966ddca49796457d..29aabdcfe909bf5204468b1f667654d1f668b882 100644 (file)
@@ -338,3 +338,10 @@ export function objectFlip(obj: any) {
   });
   return ret;
 }
+
+export function pictshareAvatarThumbnail(src: string): string {
+  // sample url: http://localhost:8535/pictshare/gs7xuu.jpg
+  let split = src.split('pictshare');
+  let out = `${split[0]}pictshare/96x96${split[1]}`;
+  return out;
+}
index da4cd3aa629e9e31fd440c766a0b1a41824d56a0..1975cfa5d6c42432bf29e8c83c18f6e8483943db 100644 (file)
@@ -10,15 +10,15 @@ import { nl } from './src/translations/nl';
 import { it } from './src/translations/it';
 
 let files = [
-  {t: de, n: 'de'},
-  {t: eo, n: 'eo'},
-  {t: es, n: 'es'},
-  {t: fr, n: 'fr'},
-  {t: it, n: 'it'},
-  {t: nl, n: 'nl'},
-  {t: ru, n: 'ru'},
-  {t: sv, n: 'sv'},
-  {t: zh, n: 'zh'},
+  { t: de, n: 'de' },
+  { t: eo, n: 'eo' },
+  { t: es, n: 'es' },
+  { t: fr, n: 'fr' },
+  { t: it, n: 'it' },
+  { t: nl, n: 'nl' },
+  { t: ru, n: 'ru' },
+  { t: sv, n: 'sv' },
+  { t: zh, n: 'zh' },
 ];
 let masterKeys = Object.keys(en.translation);
 
@@ -27,7 +27,7 @@ report += '--- | --- | ---\n';
 
 for (let file of files) {
   let keys = Object.keys(file.t.translation);
-  let pct: number = (keys.length / masterKeys.length * 100);
+  let pct: number = (keys.length / masterKeys.length) * 100;
   let missing = difference(masterKeys, keys);
   report += `${file.n} | ${pct.toFixed(0)}% | ${missing} \n`;
 }