Browse Source

Adding user avatars / icons. Requires pictshare.

- Fixes #188
avatar
Dessalines 1 year ago
parent
commit
a4428528e3
32 changed files with 721 additions and 47 deletions
  1. +9
    -9
      README.md
  2. +224
    -0
      server/migrations/2019-12-29-164820_add_avatar/down.sql
  3. +234
    -0
      server/migrations/2019-12-29-164820_add_avatar/up.sql
  4. +12
    -1
      server/src/api/user.rs
  5. +1
    -1
      server/src/apub/mod.rs
  6. +1
    -0
      server/src/db/comment.rs
  7. +7
    -0
      server/src/db/comment_view.rs
  8. +1
    -0
      server/src/db/community.rs
  9. +8
    -0
      server/src/db/community_view.rs
  10. +2
    -0
      server/src/db/moderator.rs
  11. +1
    -0
      server/src/db/password_reset_request.rs
  12. +1
    -0
      server/src/db/post.rs
  13. +5
    -0
      server/src/db/post_view.rs
  14. +2
    -0
      server/src/db/site_view.rs
  15. +6
    -2
      server/src/db/user.rs
  16. +2
    -0
      server/src/db/user_mention.rs
  17. +3
    -1
      server/src/db/user_mention_view.rs
  18. +2
    -0
      server/src/db/user_view.rs
  19. +1
    -1
      server/src/schema.rs
  20. +2
    -0
      ui/.eslintignore
  21. +14
    -12
      ui/fuse.js
  22. +16
    -2
      ui/src/components/comment-node.tsx
  23. +13
    -1
      ui/src/components/main.tsx
  24. +14
    -2
      ui/src/components/navbar.tsx
  25. +10
    -1
      ui/src/components/post-listing.tsx
  26. +14
    -1
      ui/src/components/search.tsx
  27. +10
    -2
      ui/src/components/sidebar.tsx
  28. +81
    -1
      ui/src/components/user.tsx
  29. +7
    -0
      ui/src/interfaces.ts
  30. +1
    -0
      ui/src/translations/en.ts
  31. +7
    -0
      ui/src/utils.ts
  32. +10
    -10
      ui/translation_report.ts

+ 9
- 9
README.md View File

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


+ 224
- 0
server/migrations/2019-12-29-164820_add_avatar/down.sql View File

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

+ 234
- 0
server/migrations/2019-12-29-164820_add_avatar/up.sql View File

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

+ 12
- 1
server/src/api/user.rs View 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()),


+ 1
- 1
server/src/apub/mod.rs View 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,


+ 1
- 0
server/src/db/comment.rs View File

@ -174,6 +174,7 @@ mod tests {
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
avatar: None,
admin: false,
banned: false,
updated: None,


+ 7
- 0
server/src/db/comment_view.rs View 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,


+ 1
- 0
server/src/db/community.rs View File

@ -220,6 +220,7 @@ mod tests {
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
avatar: None,
admin: false,
banned: false,
updated: None,


+ 8
- 0
server/src/db/community_view.rs View 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,
}


+ 2
- 0
server/src/db/moderator.rs View 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,


+ 1
- 0
server/src/db/password_reset_request.rs View File

@ -92,6 +92,7 @@ mod tests {
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
avatar: None,
admin: false,
banned: false,
updated: None,


+ 1
- 0
server/src/db/post.rs View File

@ -187,6 +187,7 @@ mod tests {
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
avatar: None,
admin: false,
banned: false,
updated: None,


+ 5
- 0
server/src/db/post_view.rs View 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,


+ 2
- 0
server/src/db/site_view.rs View 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,


+ 6
- 2
server/src/db/user.rs View 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,


+ 2
- 0
server/src/db/user_mention.rs View 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,


+ 3
- 1
server/src/db/user_mention_view.rs View 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,


+ 2
- 0
server/src/db/user_view.rs View 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,


+ 1
- 1
server/src/schema.rs View 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,


+ 2
- 0
ui/.eslintignore View File

@ -0,0 +1,2 @@
fuse.js
translation_report.ts

+ 14
- 12
ui/fuse.js View 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();


+ 16
- 2
ui/src/components/comment-node.tsx View 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 && (


+ 13
- 1
ui/src/components/main.tsx View 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>
))}


+ 14
- 2
ui/src/components/navbar.tsx View 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>
</>


+ 10
- 1
ui/src/components/post-listing.tsx View 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">


+ 14
- 1
ui/src/components/search.tsx View 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


+ 10
- 2
ui/src/components/sidebar.tsx View 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>
))}


+ 81
- 1
ui/src/components/user.tsx View 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);


+ 7
- 0
ui/src/interfaces.ts View 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;