mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-24 23:30:56 +00:00
Merge branch 'master' into migrate-apub-lib
This commit is contained in:
commit
d720993141
2
ansible/VERSION
vendored
2
ansible/VERSION
vendored
|
@ -1 +1 @@
|
|||
v0.7.11
|
||||
v0.7.13
|
||||
|
|
2
docker/prod/docker-compose.yml
vendored
2
docker/prod/docker-compose.yml
vendored
|
@ -12,7 +12,7 @@ services:
|
|||
restart: always
|
||||
|
||||
lemmy:
|
||||
image: dessalines/lemmy:v0.7.11
|
||||
image: dessalines/lemmy:v0.7.13
|
||||
ports:
|
||||
- "127.0.0.1:8536:8536"
|
||||
restart: always
|
||||
|
|
2
docs/src/administration_configuration.md
vendored
2
docs/src/administration_configuration.md
vendored
|
@ -7,7 +7,7 @@ can copy the options you want to change into your local `config.hjson` file.
|
|||
|
||||
Additionally, you can override any config files with environment variables. These have the same
|
||||
name as the config options, and are prefixed with `LEMMY_`. For example, you can override the
|
||||
`database.password` with `LEMMY__DATABASE__POOL_SIZE=10`.
|
||||
`database.password` with `LEMMY_DATABASE__POOL_SIZE=10`.
|
||||
|
||||
An additional option `LEMMY_DATABASE_URL` is available, which can be used with a PostgreSQL
|
||||
connection string like `postgres://lemmy:password@lemmy_db:5432/lemmy`, passing all connection
|
||||
|
|
65
docs/src/lemmy_council.md
vendored
65
docs/src/lemmy_council.md
vendored
|
@ -3,30 +3,52 @@
|
|||
- A group of lemmy developers and users that use a well-defined democratic process to steer the project in a positive direction, keep it aligned to community goals, and resolve conflicts.
|
||||
- Council members are also added as administrators to any official Lemmy instances.
|
||||
|
||||
## Voting / Decision-Making
|
||||
## 1. What gets voted on
|
||||
|
||||
### Process
|
||||
- Anything is open for discussion
|
||||
- Voting done through matrix chat reacts (thumbs up/thumbs down)
|
||||
- Require a simple majority for votes. (Maybe 2/3rds for more debated decisions).
|
||||
- Once a decision is reached democratically, the dicision is binding and all group members have to follow it
|
||||
- All members of the Lemmy council have equal voting power.
|
||||
- Voting must stay open for at least 2 days.
|
||||
This section describes all the aspects of Lemmy where the council has decision making power, namely:
|
||||
|
||||
### What gets voted on
|
||||
- Membership (joining, removing)
|
||||
- Coding direction
|
||||
- Priorities / Emphasis
|
||||
- Controversial features (For example, an unpopular feature should be removed)
|
||||
- Communication mediums
|
||||
- Conflict resolution
|
||||
- dev.lemmy.ml (domain and server)
|
||||
- lemmy.ml and subdomains (excluding communism.lemmy.ml)
|
||||
- git repo including mirrors (on github, gitea, etc)
|
||||
- Any official accounts of the Lemmy project, for example the Mastodon account or the Liberapay account
|
||||
- Moderation and conflict resolution on:
|
||||
- [dev.lemmy.ml](https://dev.lemmy.ml/)
|
||||
- [github.com/LemmyNet/lemmy](https://github.com/LemmyNet/lemmy)
|
||||
- [yerbamate.dev/LemmyNet/lemmy](https://yerbamate.dev/LemmyNet/lemmy)
|
||||
- [weblate.yerbamate.dev/projects/lemmy/](https://weblate.yerbamate.dev/projects/lemmy/)
|
||||
- Technical administration of dev.lemmy.ml
|
||||
- Official Lemmy accounts
|
||||
- [Mastodon](https://mastodon.social/@LemmyDev)
|
||||
- [Liberapay](https://liberapay.com/Lemmy/)
|
||||
- [Patreon](https://www.patreon.com/dessalines)
|
||||
- Council membership changes
|
||||
- Changes to these rules
|
||||
|
||||
## Joining
|
||||
## 2. Feedback and Activity Reports
|
||||
|
||||
Every week, the council should make a thread on Lemmy that details its activity during the past week, be it development, moderation, or anything else mentioned in 1.
|
||||
|
||||
At the same time, users can give feedback and suggestions in this thread. This should be taken into account by the council. Council members can call for a vote on any controversial issues, if they can't be resolved by discussion.
|
||||
|
||||
## 2. Voting Process
|
||||
|
||||
Most of the time, we keep each other up to date through the Matrix chat, and take informal decisions on uncontroversial issues. For example, a user clearly violating the site rules could be banned by a single person, or ideally after discussing it with at least one other member.
|
||||
|
||||
If an issue can not be resolved in this way, then any council member can call for a vote, which works in the following way:
|
||||
|
||||
- Any council member can call for a vote, on any topic mentioned in 1.
|
||||
- This should be used if there is any controversy in the community, or between council members.
|
||||
- Before taking any decision, there needs to be a discussion where every council member can
|
||||
explain their position.
|
||||
- Discussion should be taken with the goal of reaching a compromise that is acceptable for
|
||||
everyone.
|
||||
- After the discussion, voting is done through Matrix emojis (👍: yes, 👎: no, X: abstain) and must
|
||||
stay open for at least two days.
|
||||
- All members of the Lemmy council have equal voting power.
|
||||
- Decisions should be reached unanimously, or nearly so. If this is not possible, at least
|
||||
2/3 of votes must be in favour for the motion to pass.
|
||||
- Once a decision is reached in this way, every member needs to abide by it.
|
||||
|
||||
## 4. Joining
|
||||
- We use the following process: anyone who is active around Lemmy can recommend any other active person to join the council. This has to be approved by a majority of the council.
|
||||
- Active users are defined as those who contribute to Lemmy in some way for at least an hour per week on average, doing things like reporting bugs, discussing rules and features, translating, promoting, developing, or doing other things that aim to improve Lemmy as a whole.
|
||||
-> people should have joined at least a month ago.
|
||||
|
@ -34,23 +56,24 @@
|
|||
- Note: we would like to have a process where community members can elect candidates for the council, but this is not realistic because a single user could easily create multiple accounts and cheat the vote.
|
||||
- Limit growth to one new member per month at most.
|
||||
|
||||
## Removing members
|
||||
## 5. Removing members
|
||||
- Inactive members should be removed from the council after a few months of inactivity, and after receiving a notification about this.
|
||||
- Members that dont follow binding council decisions should be removed.
|
||||
- Any member can be removed in a vote.
|
||||
|
||||
## Goals
|
||||
## 6. Goals
|
||||
- We encourage the membership of groups such as LGBT, religious or ethnic minorities, abuse victims, etc etc, and strive to create a safe space for them to express their opinions. We also support measures to increase participation by the previously mentioned groups.
|
||||
- The following are banned, and will always be harshly punished: fascism, abuse, racism, sexism, etc etc,
|
||||
|
||||
## Communication
|
||||
## 7. Communication
|
||||
- A private Matrix chat for all council members.
|
||||
- (Once private communities are done) A private community on dev.lemmy.ml for issues.
|
||||
|
||||
## Member List / Contact Info
|
||||
## 8. Member List / Contact Info
|
||||
General Contact [@LemmyDev Mastodon](https://mastodon.social/@LemmyDev)
|
||||
|
||||
- [Dessalines](https://dev.lemmy.ml/u/dessalines)
|
||||
- [Nutomic](https://dev.lemmy.ml/u/nutomic)
|
||||
- [AgreeableLandscape](https://dev.lemmy.ml/u/AgreeableLandscape)
|
||||
- [fruechtchen](https://dev.lemmy.ml/u/fruechtchen)
|
||||
- [kixiQu](https://dev.lemmy.ml/u/kixiQu)
|
||||
|
|
535
server/migrations/2020-06-30-135809_remove_mat_views/down.sql
vendored
Normal file
535
server/migrations/2020-06-30-135809_remove_mat_views/down.sql
vendored
Normal file
|
@ -0,0 +1,535 @@
|
|||
-- Dropping all the fast tables
|
||||
drop table user_fast;
|
||||
drop view post_fast_view;
|
||||
drop table post_aggregates_fast;
|
||||
drop view community_fast_view;
|
||||
drop table community_aggregates_fast;
|
||||
drop view reply_fast_view;
|
||||
drop view user_mention_fast_view;
|
||||
drop view comment_fast_view;
|
||||
drop table comment_aggregates_fast;
|
||||
|
||||
-- Re-adding all the triggers, functions, and mviews
|
||||
|
||||
-- private message
|
||||
create materialized view private_message_mview as select * from private_message_view;
|
||||
|
||||
create unique index idx_private_message_mview_id on private_message_mview (id);
|
||||
|
||||
|
||||
-- Create the triggers
|
||||
create or replace function refresh_private_message()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
refresh materialized view concurrently private_message_mview;
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
create trigger refresh_private_message
|
||||
after insert or update or delete or truncate
|
||||
on private_message
|
||||
for each statement
|
||||
execute procedure refresh_private_message();
|
||||
|
||||
-- user
|
||||
create or replace function refresh_user()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
refresh materialized view concurrently user_mview;
|
||||
refresh materialized view concurrently comment_aggregates_mview; -- cause of bans
|
||||
refresh materialized view concurrently post_aggregates_mview;
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
drop trigger refresh_user on user_;
|
||||
create trigger refresh_user
|
||||
after insert or update or delete or truncate
|
||||
on user_
|
||||
for each statement
|
||||
execute procedure refresh_user();
|
||||
drop view user_view cascade;
|
||||
|
||||
create view user_view as
|
||||
select
|
||||
u.id,
|
||||
u.actor_id,
|
||||
u.name,
|
||||
u.avatar,
|
||||
u.email,
|
||||
u.matrix_user_id,
|
||||
u.bio,
|
||||
u.local,
|
||||
u.admin,
|
||||
u.banned,
|
||||
u.show_avatars,
|
||||
u.send_notifications_to_email,
|
||||
u.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;
|
||||
|
||||
create materialized view user_mview as select * from user_view;
|
||||
|
||||
create unique index idx_user_mview_id on user_mview (id);
|
||||
|
||||
-- community
|
||||
drop trigger refresh_community on community;
|
||||
create trigger refresh_community
|
||||
after insert or update or delete or truncate
|
||||
on community
|
||||
for each statement
|
||||
execute procedure refresh_community();
|
||||
|
||||
create or replace function refresh_community()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
refresh materialized view concurrently post_aggregates_mview;
|
||||
refresh materialized view concurrently community_aggregates_mview;
|
||||
refresh materialized view concurrently user_mview;
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
drop view community_aggregates_view cascade;
|
||||
create view community_aggregates_view as
|
||||
-- Now that there's public and private keys, you have to be explicit here
|
||||
select c.id,
|
||||
c.name,
|
||||
c.title,
|
||||
c.description,
|
||||
c.category_id,
|
||||
c.creator_id,
|
||||
c.removed,
|
||||
c.published,
|
||||
c.updated,
|
||||
c.deleted,
|
||||
c.nsfw,
|
||||
c.actor_id,
|
||||
c.local,
|
||||
c.last_refreshed_at,
|
||||
(select actor_id from user_ u where c.creator_id = u.id) as creator_actor_id,
|
||||
(select local from user_ u where c.creator_id = u.id) as creator_local,
|
||||
(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;
|
||||
|
||||
create materialized view community_aggregates_mview as select * from community_aggregates_view;
|
||||
|
||||
create unique index idx_community_aggregates_mview_id on community_aggregates_mview (id);
|
||||
|
||||
create view community_view as
|
||||
with all_community as
|
||||
(
|
||||
select
|
||||
ca.*
|
||||
from community_aggregates_view ca
|
||||
)
|
||||
|
||||
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
|
||||
;
|
||||
|
||||
create view community_mview as
|
||||
with all_community as
|
||||
(
|
||||
select
|
||||
ca.*
|
||||
from community_aggregates_mview ca
|
||||
)
|
||||
|
||||
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
|
||||
;
|
||||
-- Post
|
||||
drop view post_view;
|
||||
drop view post_aggregates_view;
|
||||
|
||||
-- regen post view
|
||||
create view post_aggregates_view 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 actor_id from user_ where p.creator_id = user_.id) as creator_actor_id,
|
||||
(select local from user_ where p.creator_id = user_.id) as creator_local,
|
||||
(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 actor_id from community where p.community_id = community.id) as community_actor_id,
|
||||
(select local from community where p.community_id = community.id) as community_local,
|
||||
(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),
|
||||
(
|
||||
case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps
|
||||
else greatest(c.recent_comment_time, p.published)
|
||||
end
|
||||
)
|
||||
) as hot_rank,
|
||||
(
|
||||
case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps
|
||||
else greatest(c.recent_comment_time, p.published)
|
||||
end
|
||||
) as newest_activity_time
|
||||
from post p
|
||||
left join post_like pl on p.id = pl.post_id
|
||||
left join (
|
||||
select post_id,
|
||||
max(published) as recent_comment_time
|
||||
from comment
|
||||
group by 1
|
||||
) c on p.id = c.post_id
|
||||
group by p.id, c.recent_comment_time;
|
||||
|
||||
create materialized view post_aggregates_mview as select * from post_aggregates_view;
|
||||
|
||||
create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id);
|
||||
|
||||
create view post_view as
|
||||
with all_post as (
|
||||
select
|
||||
pa.*
|
||||
from post_aggregates_view pa
|
||||
)
|
||||
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
|
||||
;
|
||||
|
||||
create view post_mview as
|
||||
with all_post as (
|
||||
select
|
||||
pa.*
|
||||
from post_aggregates_mview pa
|
||||
)
|
||||
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
|
||||
;
|
||||
|
||||
drop trigger refresh_post on post;
|
||||
create trigger refresh_post
|
||||
after insert or update or delete or truncate
|
||||
on post
|
||||
for each statement
|
||||
execute procedure refresh_post();
|
||||
|
||||
create or replace function refresh_post()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
refresh materialized view concurrently post_aggregates_mview;
|
||||
refresh materialized view concurrently user_mview;
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
|
||||
-- User mention, comment, reply
|
||||
drop view user_mention_view;
|
||||
drop view comment_view;
|
||||
drop view comment_aggregates_view;
|
||||
|
||||
-- reply and comment view
|
||||
create view comment_aggregates_view as
|
||||
select
|
||||
c.*,
|
||||
(select community_id from post p where p.id = c.post_id),
|
||||
(select co.actor_id from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_actor_id,
|
||||
(select co.local from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_local,
|
||||
(select co.name from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_name,
|
||||
(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 actor_id from user_ where c.creator_id = user_.id) as creator_actor_id,
|
||||
(select local from user_ where c.creator_id = user_.id) as creator_local,
|
||||
(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,
|
||||
hot_rank(coalesce(sum(cl.score) , 0), c.published) as hot_rank
|
||||
from comment c
|
||||
left join comment_like cl on c.id = cl.comment_id
|
||||
group by c.id;
|
||||
|
||||
create materialized view comment_aggregates_mview as select * from comment_aggregates_view;
|
||||
|
||||
create unique index idx_comment_aggregates_mview_id on comment_aggregates_mview (id);
|
||||
|
||||
create view comment_view as
|
||||
with all_comment as
|
||||
(
|
||||
select
|
||||
ca.*
|
||||
from comment_aggregates_view ca
|
||||
)
|
||||
|
||||
select
|
||||
ac.*,
|
||||
u.id as user_id,
|
||||
coalesce(cl.score, 0) as my_vote,
|
||||
(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed,
|
||||
(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 subscribed,
|
||||
null as saved
|
||||
from all_comment ac
|
||||
;
|
||||
|
||||
create view comment_mview as
|
||||
with all_comment as
|
||||
(
|
||||
select
|
||||
ca.*
|
||||
from comment_aggregates_mview ca
|
||||
)
|
||||
|
||||
select
|
||||
ac.*,
|
||||
u.id as user_id,
|
||||
coalesce(cl.score, 0) as my_vote,
|
||||
(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed,
|
||||
(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 subscribed,
|
||||
null as saved
|
||||
from all_comment ac
|
||||
;
|
||||
|
||||
-- Do the reply_view referencing the comment_mview
|
||||
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_mview 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.creator_actor_id,
|
||||
c.creator_local,
|
||||
c.post_id,
|
||||
c.parent_id,
|
||||
c.content,
|
||||
c.removed,
|
||||
um.read,
|
||||
c.published,
|
||||
c.updated,
|
||||
c.deleted,
|
||||
c.community_id,
|
||||
c.community_actor_id,
|
||||
c.community_local,
|
||||
c.community_name,
|
||||
c.banned,
|
||||
c.banned_from_community,
|
||||
c.creator_name,
|
||||
c.creator_avatar,
|
||||
c.score,
|
||||
c.upvotes,
|
||||
c.downvotes,
|
||||
c.hot_rank,
|
||||
c.user_id,
|
||||
c.my_vote,
|
||||
c.saved,
|
||||
um.recipient_id,
|
||||
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||
from user_mention um, comment_view c
|
||||
where um.comment_id = c.id;
|
||||
|
||||
|
||||
create view user_mention_mview as
|
||||
with all_comment as
|
||||
(
|
||||
select
|
||||
ca.*
|
||||
from comment_aggregates_mview ca
|
||||
)
|
||||
|
||||
select
|
||||
ac.id,
|
||||
um.id as user_mention_id,
|
||||
ac.creator_id,
|
||||
ac.creator_actor_id,
|
||||
ac.creator_local,
|
||||
ac.post_id,
|
||||
ac.parent_id,
|
||||
ac.content,
|
||||
ac.removed,
|
||||
um.read,
|
||||
ac.published,
|
||||
ac.updated,
|
||||
ac.deleted,
|
||||
ac.community_id,
|
||||
ac.community_actor_id,
|
||||
ac.community_local,
|
||||
ac.community_name,
|
||||
ac.banned,
|
||||
ac.banned_from_community,
|
||||
ac.creator_name,
|
||||
ac.creator_avatar,
|
||||
ac.score,
|
||||
ac.upvotes,
|
||||
ac.downvotes,
|
||||
ac.hot_rank,
|
||||
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,
|
||||
um.recipient_id,
|
||||
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||
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
|
||||
left join user_mention um on um.comment_id = ac.id
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
ac.id,
|
||||
um.id as user_mention_id,
|
||||
ac.creator_id,
|
||||
ac.creator_actor_id,
|
||||
ac.creator_local,
|
||||
ac.post_id,
|
||||
ac.parent_id,
|
||||
ac.content,
|
||||
ac.removed,
|
||||
um.read,
|
||||
ac.published,
|
||||
ac.updated,
|
||||
ac.deleted,
|
||||
ac.community_id,
|
||||
ac.community_actor_id,
|
||||
ac.community_local,
|
||||
ac.community_name,
|
||||
ac.banned,
|
||||
ac.banned_from_community,
|
||||
ac.creator_name,
|
||||
ac.creator_avatar,
|
||||
ac.score,
|
||||
ac.upvotes,
|
||||
ac.downvotes,
|
||||
ac.hot_rank,
|
||||
null as user_id,
|
||||
null as my_vote,
|
||||
null as saved,
|
||||
um.recipient_id,
|
||||
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||
from all_comment ac
|
||||
left join user_mention um on um.comment_id = ac.id
|
||||
;
|
||||
|
939
server/migrations/2020-06-30-135809_remove_mat_views/up.sql
vendored
Normal file
939
server/migrations/2020-06-30-135809_remove_mat_views/up.sql
vendored
Normal file
|
@ -0,0 +1,939 @@
|
|||
-- Drop the mviews
|
||||
drop view post_mview;
|
||||
drop materialized view user_mview;
|
||||
drop view community_mview;
|
||||
drop materialized view private_message_mview;
|
||||
drop view user_mention_mview;
|
||||
drop view reply_view;
|
||||
drop view comment_mview;
|
||||
drop materialized view post_aggregates_mview;
|
||||
drop materialized view community_aggregates_mview;
|
||||
drop materialized view comment_aggregates_mview;
|
||||
drop trigger refresh_private_message on private_message;
|
||||
|
||||
-- User
|
||||
drop view user_view;
|
||||
create view user_view as
|
||||
select
|
||||
u.id,
|
||||
u.actor_id,
|
||||
u.name,
|
||||
u.avatar,
|
||||
u.email,
|
||||
u.matrix_user_id,
|
||||
u.bio,
|
||||
u.local,
|
||||
u.admin,
|
||||
u.banned,
|
||||
u.show_avatars,
|
||||
u.send_notifications_to_email,
|
||||
u.published,
|
||||
coalesce(pd.posts, 0) as number_of_posts,
|
||||
coalesce(pd.score, 0) as post_score,
|
||||
coalesce(cd.comments, 0) as number_of_comments,
|
||||
coalesce(cd.score, 0) as comment_score
|
||||
from user_ u
|
||||
left join (
|
||||
select
|
||||
p.creator_id as creator_id,
|
||||
count(distinct p.id) as posts,
|
||||
sum(pl.score) as score
|
||||
from post p
|
||||
join post_like pl on p.id = pl.post_id
|
||||
group by p.creator_id
|
||||
) pd on u.id = pd.creator_id
|
||||
left join (
|
||||
select
|
||||
c.creator_id,
|
||||
count(distinct c.id) as comments,
|
||||
sum(cl.score) as score
|
||||
from comment c
|
||||
join comment_like cl on c.id = cl.comment_id
|
||||
group by c.creator_id
|
||||
) cd on u.id = cd.creator_id;
|
||||
|
||||
|
||||
create table user_fast as select * from user_view;
|
||||
alter table user_fast add primary key (id);
|
||||
|
||||
drop trigger refresh_user on user_;
|
||||
|
||||
create trigger refresh_user
|
||||
after insert or update or delete
|
||||
on user_
|
||||
for each row
|
||||
execute procedure refresh_user();
|
||||
|
||||
-- Sample insert
|
||||
-- insert into user_(name, password_encrypted) values ('test_name', 'bleh');
|
||||
-- Sample delete
|
||||
-- delete from user_ where name like 'test_name';
|
||||
-- Sample update
|
||||
-- update user_ set avatar = 'hai' where name like 'test_name';
|
||||
create or replace function refresh_user()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
IF (TG_OP = 'DELETE') THEN
|
||||
delete from user_fast where id = OLD.id;
|
||||
ELSIF (TG_OP = 'UPDATE') THEN
|
||||
delete from user_fast where id = OLD.id;
|
||||
insert into user_fast select * from user_view where id = NEW.id;
|
||||
|
||||
-- Refresh post_fast, cause of user info changes
|
||||
delete from post_aggregates_fast where creator_id = NEW.id;
|
||||
insert into post_aggregates_fast select * from post_aggregates_view where creator_id = NEW.id;
|
||||
|
||||
delete from comment_aggregates_fast where creator_id = NEW.id;
|
||||
insert into comment_aggregates_fast select * from comment_aggregates_view where creator_id = NEW.id;
|
||||
|
||||
ELSIF (TG_OP = 'INSERT') THEN
|
||||
insert into user_fast select * from user_view where id = NEW.id;
|
||||
END IF;
|
||||
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
-- Post
|
||||
-- Redoing the views : Credit eiknat
|
||||
drop view post_view;
|
||||
drop view post_aggregates_view;
|
||||
|
||||
create view post_aggregates_view as
|
||||
select
|
||||
p.*,
|
||||
-- creator details
|
||||
u.actor_id as creator_actor_id,
|
||||
u."local" as creator_local,
|
||||
u."name" as creator_name,
|
||||
u.avatar as creator_avatar,
|
||||
u.banned as banned,
|
||||
cb.id::bool as banned_from_community,
|
||||
-- community details
|
||||
c.actor_id as community_actor_id,
|
||||
c."local" as community_local,
|
||||
c."name" as community_name,
|
||||
c.removed as community_removed,
|
||||
c.deleted as community_deleted,
|
||||
c.nsfw as community_nsfw,
|
||||
-- post score data/comment count
|
||||
coalesce(ct.comments, 0) as number_of_comments,
|
||||
coalesce(pl.score, 0) as score,
|
||||
coalesce(pl.upvotes, 0) as upvotes,
|
||||
coalesce(pl.downvotes, 0) as downvotes,
|
||||
hot_rank(
|
||||
coalesce(pl.score , 0), (
|
||||
case
|
||||
when (p.published < ('now'::timestamp - '1 month'::interval))
|
||||
then p.published
|
||||
else greatest(ct.recent_comment_time, p.published)
|
||||
end
|
||||
)
|
||||
) as hot_rank,
|
||||
(
|
||||
case
|
||||
when (p.published < ('now'::timestamp - '1 month'::interval))
|
||||
then p.published
|
||||
else greatest(ct.recent_comment_time, p.published)
|
||||
end
|
||||
) as newest_activity_time
|
||||
from post p
|
||||
left join user_ u on p.creator_id = u.id
|
||||
left join community_user_ban cb on p.creator_id = cb.user_id and p.community_id = cb.community_id
|
||||
left join community c on p.community_id = c.id
|
||||
left join (
|
||||
select
|
||||
post_id,
|
||||
count(*) as comments,
|
||||
max(published) as recent_comment_time
|
||||
from comment
|
||||
group by post_id
|
||||
) ct on ct.post_id = p.id
|
||||
left join (
|
||||
select
|
||||
post_id,
|
||||
sum(score) as score,
|
||||
sum(score) filter (where score = 1) as upvotes,
|
||||
-sum(score) filter (where score = -1) as downvotes
|
||||
from post_like
|
||||
group by post_id
|
||||
) pl on pl.post_id = p.id
|
||||
order by p.id;
|
||||
|
||||
create view post_view as
|
||||
select
|
||||
pav.*,
|
||||
us.id as user_id,
|
||||
us.user_vote as my_vote,
|
||||
us.is_subbed::bool as subscribed,
|
||||
us.is_read::bool as read,
|
||||
us.is_saved::bool as saved
|
||||
from post_aggregates_view pav
|
||||
cross join lateral (
|
||||
select
|
||||
u.id,
|
||||
coalesce(cf.community_id, 0) as is_subbed,
|
||||
coalesce(pr.post_id, 0) as is_read,
|
||||
coalesce(ps.post_id, 0) as is_saved,
|
||||
coalesce(pl.score, 0) as user_vote
|
||||
from user_ u
|
||||
left join community_user_ban cb on u.id = cb.user_id and cb.community_id = pav.community_id
|
||||
left join community_follower cf on u.id = cf.user_id and cf.community_id = pav.community_id
|
||||
left join post_read pr on u.id = pr.user_id and pr.post_id = pav.id
|
||||
left join post_saved ps on u.id = ps.user_id and ps.post_id = pav.id
|
||||
left join post_like pl on u.id = pl.user_id and pav.id = pl.post_id
|
||||
) as us
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
pav.*,
|
||||
null as user_id,
|
||||
null as my_vote,
|
||||
null as subscribed,
|
||||
null as read,
|
||||
null as saved
|
||||
from post_aggregates_view pav;
|
||||
|
||||
-- The post fast table
|
||||
create table post_aggregates_fast as select * from post_aggregates_view;
|
||||
alter table post_aggregates_fast add primary key (id);
|
||||
|
||||
-- For the hot rank resorting
|
||||
create index idx_post_aggregates_fast_hot_rank_published on post_aggregates_fast (hot_rank desc, published desc);
|
||||
|
||||
create view post_fast_view as
|
||||
select
|
||||
pav.*,
|
||||
us.id as user_id,
|
||||
us.user_vote as my_vote,
|
||||
us.is_subbed::bool as subscribed,
|
||||
us.is_read::bool as read,
|
||||
us.is_saved::bool as saved
|
||||
from post_aggregates_fast pav
|
||||
cross join lateral (
|
||||
select
|
||||
u.id,
|
||||
coalesce(cf.community_id, 0) as is_subbed,
|
||||
coalesce(pr.post_id, 0) as is_read,
|
||||
coalesce(ps.post_id, 0) as is_saved,
|
||||
coalesce(pl.score, 0) as user_vote
|
||||
from user_ u
|
||||
left join community_user_ban cb on u.id = cb.user_id and cb.community_id = pav.community_id
|
||||
left join community_follower cf on u.id = cf.user_id and cf.community_id = pav.community_id
|
||||
left join post_read pr on u.id = pr.user_id and pr.post_id = pav.id
|
||||
left join post_saved ps on u.id = ps.user_id and ps.post_id = pav.id
|
||||
left join post_like pl on u.id = pl.user_id and pav.id = pl.post_id
|
||||
) as us
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
pav.*,
|
||||
null as user_id,
|
||||
null as my_vote,
|
||||
null as subscribed,
|
||||
null as read,
|
||||
null as saved
|
||||
from post_aggregates_fast pav;
|
||||
|
||||
drop trigger refresh_post on post;
|
||||
|
||||
create trigger refresh_post
|
||||
after insert or update or delete
|
||||
on post
|
||||
for each row
|
||||
execute procedure refresh_post();
|
||||
|
||||
-- Sample select
|
||||
-- select id, name from post_fast_view where name like 'test_post' and user_id is null;
|
||||
-- Sample insert
|
||||
-- insert into post(name, creator_id, community_id) values ('test_post', 2, 2);
|
||||
-- Sample delete
|
||||
-- delete from post where name like 'test_post';
|
||||
-- Sample update
|
||||
-- update post set community_id = 4 where name like 'test_post';
|
||||
create or replace function refresh_post()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
IF (TG_OP = 'DELETE') THEN
|
||||
delete from post_aggregates_fast where id = OLD.id;
|
||||
|
||||
-- Update community number of posts
|
||||
update community_aggregates_fast set number_of_posts = number_of_posts - 1 where id = OLD.community_id;
|
||||
ELSIF (TG_OP = 'UPDATE') THEN
|
||||
delete from post_aggregates_fast where id = OLD.id;
|
||||
insert into post_aggregates_fast select * from post_aggregates_view where id = NEW.id;
|
||||
ELSIF (TG_OP = 'INSERT') THEN
|
||||
insert into post_aggregates_fast select * from post_aggregates_view where id = NEW.id;
|
||||
|
||||
-- Update that users number of posts, post score
|
||||
delete from user_fast where id = NEW.creator_id;
|
||||
insert into user_fast select * from user_view where id = NEW.creator_id;
|
||||
|
||||
-- Update community number of posts
|
||||
update community_aggregates_fast set number_of_posts = number_of_posts + 1 where id = NEW.community_id;
|
||||
|
||||
-- Update the hot rank on the post table
|
||||
-- TODO this might not correctly update it, using a 1 week interval
|
||||
update post_aggregates_fast as paf
|
||||
set hot_rank = pav.hot_rank
|
||||
from post_aggregates_view as pav
|
||||
where paf.id = pav.id and (pav.published > ('now'::timestamp - '1 week'::interval));
|
||||
END IF;
|
||||
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
-- Community
|
||||
-- Redoing the views : Credit eiknat
|
||||
drop view community_moderator_view;
|
||||
drop view community_follower_view;
|
||||
drop view community_user_ban_view;
|
||||
drop view community_view;
|
||||
drop view community_aggregates_view;
|
||||
|
||||
create view community_aggregates_view as
|
||||
select
|
||||
c.id,
|
||||
c.name,
|
||||
c.title,
|
||||
c.description,
|
||||
c.category_id,
|
||||
c.creator_id,
|
||||
c.removed,
|
||||
c.published,
|
||||
c.updated,
|
||||
c.deleted,
|
||||
c.nsfw,
|
||||
c.actor_id,
|
||||
c.local,
|
||||
c.last_refreshed_at,
|
||||
u.actor_id as creator_actor_id,
|
||||
u.local as creator_local,
|
||||
u.name as creator_name,
|
||||
u.avatar as creator_avatar,
|
||||
cat.name as category_name,
|
||||
coalesce(cf.subs, 0) as number_of_subscribers,
|
||||
coalesce(cd.posts, 0) as number_of_posts,
|
||||
coalesce(cd.comments, 0) as number_of_comments,
|
||||
hot_rank(cf.subs, c.published) as hot_rank
|
||||
from community c
|
||||
left join user_ u on c.creator_id = u.id
|
||||
left join category cat on c.category_id = cat.id
|
||||
left join (
|
||||
select
|
||||
p.community_id,
|
||||
count(distinct p.id) as posts,
|
||||
count(distinct ct.id) as comments
|
||||
from post p
|
||||
join comment ct on p.id = ct.post_id
|
||||
group by p.community_id
|
||||
) cd on cd.community_id = c.id
|
||||
left join (
|
||||
select
|
||||
community_id,
|
||||
count(*) as subs
|
||||
from community_follower
|
||||
group by community_id
|
||||
) cf on cf.community_id = c.id;
|
||||
|
||||
create view community_view as
|
||||
select
|
||||
cv.*,
|
||||
us.user as user_id,
|
||||
us.is_subbed::bool as subscribed
|
||||
from community_aggregates_view cv
|
||||
cross join lateral (
|
||||
select
|
||||
u.id as user,
|
||||
coalesce(cf.community_id, 0) as is_subbed
|
||||
from user_ u
|
||||
left join community_follower cf on u.id = cf.user_id and cf.community_id = cv.id
|
||||
) as us
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
cv.*,
|
||||
null as user_id,
|
||||
null as subscribed
|
||||
from community_aggregates_view cv;
|
||||
|
||||
create view community_moderator_view as
|
||||
select
|
||||
cm.*,
|
||||
u.actor_id as user_actor_id,
|
||||
u.local as user_local,
|
||||
u.name as user_name,
|
||||
u.avatar as avatar,
|
||||
c.actor_id as community_actor_id,
|
||||
c.local as community_local,
|
||||
c.name as community_name
|
||||
from community_moderator cm
|
||||
left join user_ u on cm.user_id = u.id
|
||||
left join community c on cm.community_id = c.id;
|
||||
|
||||
create view community_follower_view as
|
||||
select
|
||||
cf.*,
|
||||
u.actor_id as user_actor_id,
|
||||
u.local as user_local,
|
||||
u.name as user_name,
|
||||
u.avatar as avatar,
|
||||
c.actor_id as community_actor_id,
|
||||
c.local as community_local,
|
||||
c.name as community_name
|
||||
from community_follower cf
|
||||
left join user_ u on cf.user_id = u.id
|
||||
left join community c on cf.community_id = c.id;
|
||||
|
||||
create view community_user_ban_view as
|
||||
select
|
||||
cb.*,
|
||||
u.actor_id as user_actor_id,
|
||||
u.local as user_local,
|
||||
u.name as user_name,
|
||||
u.avatar as avatar,
|
||||
c.actor_id as community_actor_id,
|
||||
c.local as community_local,
|
||||
c.name as community_name
|
||||
from community_user_ban cb
|
||||
left join user_ u on cb.user_id = u.id
|
||||
left join community c on cb.community_id = c.id;
|
||||
|
||||
-- The community fast table
|
||||
|
||||
create table community_aggregates_fast as select * from community_aggregates_view;
|
||||
alter table community_aggregates_fast add primary key (id);
|
||||
|
||||
create view community_fast_view as
|
||||
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 (
|
||||
select
|
||||
ca.*
|
||||
from community_aggregates_fast ca
|
||||
) ac
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
caf.*,
|
||||
null as user_id,
|
||||
null as subscribed
|
||||
from community_aggregates_fast caf;
|
||||
|
||||
drop trigger refresh_community on community;
|
||||
|
||||
create trigger refresh_community
|
||||
after insert or update or delete
|
||||
on community
|
||||
for each row
|
||||
execute procedure refresh_community();
|
||||
|
||||
-- Sample select
|
||||
-- select * from community_fast_view where name like 'test_community_name' and user_id is null;
|
||||
-- Sample insert
|
||||
-- insert into community(name, title, category_id, creator_id) values ('test_community_name', 'test_community_title', 1, 2);
|
||||
-- Sample delete
|
||||
-- delete from community where name like 'test_community_name';
|
||||
-- Sample update
|
||||
-- update community set title = 'test_community_title_2' where name like 'test_community_name';
|
||||
create or replace function refresh_community()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
IF (TG_OP = 'DELETE') THEN
|
||||
delete from community_aggregates_fast where id = OLD.id;
|
||||
ELSIF (TG_OP = 'UPDATE') THEN
|
||||
delete from community_aggregates_fast where id = OLD.id;
|
||||
insert into community_aggregates_fast select * from community_aggregates_view where id = NEW.id;
|
||||
|
||||
-- Update user view due to owner changes
|
||||
delete from user_fast where id = NEW.creator_id;
|
||||
insert into user_fast select * from user_view where id = NEW.creator_id;
|
||||
|
||||
-- Update post view due to community changes
|
||||
delete from post_aggregates_fast where community_id = NEW.id;
|
||||
insert into post_aggregates_fast select * from post_aggregates_view where community_id = NEW.id;
|
||||
|
||||
-- TODO make sure this shows up in the users page ?
|
||||
ELSIF (TG_OP = 'INSERT') THEN
|
||||
insert into community_aggregates_fast select * from community_aggregates_view where id = NEW.id;
|
||||
END IF;
|
||||
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
-- Comment
|
||||
|
||||
drop view user_mention_view;
|
||||
drop view comment_view;
|
||||
drop view comment_aggregates_view;
|
||||
|
||||
create view comment_aggregates_view as
|
||||
select
|
||||
ct.*,
|
||||
-- community details
|
||||
p.community_id,
|
||||
c.actor_id as community_actor_id,
|
||||
c."local" as community_local,
|
||||
c."name" as community_name,
|
||||
-- creator details
|
||||
u.banned as banned,
|
||||
coalesce(cb.id, 0)::bool as banned_from_community,
|
||||
u.actor_id as creator_actor_id,
|
||||
u.local as creator_local,
|
||||
u.name as creator_name,
|
||||
u.avatar as creator_avatar,
|
||||
-- score details
|
||||
coalesce(cl.total, 0) as score,
|
||||
coalesce(cl.up, 0) as upvotes,
|
||||
coalesce(cl.down, 0) as downvotes,
|
||||
hot_rank(coalesce(cl.total, 0), ct.published) as hot_rank
|
||||
from comment ct
|
||||
left join post p on ct.post_id = p.id
|
||||
left join community c on p.community_id = c.id
|
||||
left join user_ u on ct.creator_id = u.id
|
||||
left join community_user_ban cb on ct.creator_id = cb.user_id and p.id = ct.post_id and p.community_id = cb.community_id
|
||||
left join (
|
||||
select
|
||||
l.comment_id as id,
|
||||
sum(l.score) as total,
|
||||
count(case when l.score = 1 then 1 else null end) as up,
|
||||
count(case when l.score = -1 then 1 else null end) as down
|
||||
from comment_like l
|
||||
group by comment_id
|
||||
) as cl on cl.id = ct.id;
|
||||
|
||||
create or replace view comment_view as (
|
||||
select
|
||||
cav.*,
|
||||
us.user_id as user_id,
|
||||
us.my_vote as my_vote,
|
||||
us.is_subbed::bool as subscribed,
|
||||
us.is_saved::bool as saved
|
||||
from comment_aggregates_view cav
|
||||
cross join lateral (
|
||||
select
|
||||
u.id as user_id,
|
||||
coalesce(cl.score, 0) as my_vote,
|
||||
coalesce(cf.id, 0) as is_subbed,
|
||||
coalesce(cs.id, 0) as is_saved
|
||||
from user_ u
|
||||
left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
|
||||
left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
|
||||
left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
|
||||
) as us
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
cav.*,
|
||||
null as user_id,
|
||||
null as my_vote,
|
||||
null as subscribed,
|
||||
null as saved
|
||||
from comment_aggregates_view cav
|
||||
);
|
||||
|
||||
-- The fast view
|
||||
create table comment_aggregates_fast as select * from comment_aggregates_view;
|
||||
alter table comment_aggregates_fast add primary key (id);
|
||||
|
||||
create view comment_fast_view as
|
||||
select
|
||||
cav.*,
|
||||
us.user_id as user_id,
|
||||
us.my_vote as my_vote,
|
||||
us.is_subbed::bool as subscribed,
|
||||
us.is_saved::bool as saved
|
||||
from comment_aggregates_fast cav
|
||||
cross join lateral (
|
||||
select
|
||||
u.id as user_id,
|
||||
coalesce(cl.score, 0) as my_vote,
|
||||
coalesce(cf.id, 0) as is_subbed,
|
||||
coalesce(cs.id, 0) as is_saved
|
||||
from user_ u
|
||||
left join comment_like cl on u.id = cl.user_id and cav.id = cl.comment_id
|
||||
left join comment_saved cs on u.id = cs.user_id and cs.comment_id = cav.id
|
||||
left join community_follower cf on u.id = cf.user_id and cav.community_id = cf.community_id
|
||||
) as us
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
cav.*,
|
||||
null as user_id,
|
||||
null as my_vote,
|
||||
null as subscribed,
|
||||
null as saved
|
||||
from comment_aggregates_fast cav;
|
||||
|
||||
-- Do the reply_view referencing the comment_fast_view
|
||||
create view reply_fast_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_fast_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.creator_actor_id,
|
||||
c.creator_local,
|
||||
c.post_id,
|
||||
c.parent_id,
|
||||
c.content,
|
||||
c.removed,
|
||||
um.read,
|
||||
c.published,
|
||||
c.updated,
|
||||
c.deleted,
|
||||
c.community_id,
|
||||
c.community_actor_id,
|
||||
c.community_local,
|
||||
c.community_name,
|
||||
c.banned,
|
||||
c.banned_from_community,
|
||||
c.creator_name,
|
||||
c.creator_avatar,
|
||||
c.score,
|
||||
c.upvotes,
|
||||
c.downvotes,
|
||||
c.hot_rank,
|
||||
c.user_id,
|
||||
c.my_vote,
|
||||
c.saved,
|
||||
um.recipient_id,
|
||||
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||
from user_mention um, comment_view c
|
||||
where um.comment_id = c.id;
|
||||
|
||||
create view user_mention_fast_view as
|
||||
select
|
||||
ac.id,
|
||||
um.id as user_mention_id,
|
||||
ac.creator_id,
|
||||
ac.creator_actor_id,
|
||||
ac.creator_local,
|
||||
ac.post_id,
|
||||
ac.parent_id,
|
||||
ac.content,
|
||||
ac.removed,
|
||||
um.read,
|
||||
ac.published,
|
||||
ac.updated,
|
||||
ac.deleted,
|
||||
ac.community_id,
|
||||
ac.community_actor_id,
|
||||
ac.community_local,
|
||||
ac.community_name,
|
||||
ac.banned,
|
||||
ac.banned_from_community,
|
||||
ac.creator_name,
|
||||
ac.creator_avatar,
|
||||
ac.score,
|
||||
ac.upvotes,
|
||||
ac.downvotes,
|
||||
ac.hot_rank,
|
||||
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,
|
||||
um.recipient_id,
|
||||
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||
from user_ u
|
||||
cross join (
|
||||
select
|
||||
ca.*
|
||||
from comment_aggregates_fast ca
|
||||
) ac
|
||||
left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
|
||||
left join user_mention um on um.comment_id = ac.id
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
ac.id,
|
||||
um.id as user_mention_id,
|
||||
ac.creator_id,
|
||||
ac.creator_actor_id,
|
||||
ac.creator_local,
|
||||
ac.post_id,
|
||||
ac.parent_id,
|
||||
ac.content,
|
||||
ac.removed,
|
||||
um.read,
|
||||
ac.published,
|
||||
ac.updated,
|
||||
ac.deleted,
|
||||
ac.community_id,
|
||||
ac.community_actor_id,
|
||||
ac.community_local,
|
||||
ac.community_name,
|
||||
ac.banned,
|
||||
ac.banned_from_community,
|
||||
ac.creator_name,
|
||||
ac.creator_avatar,
|
||||
ac.score,
|
||||
ac.upvotes,
|
||||
ac.downvotes,
|
||||
ac.hot_rank,
|
||||
null as user_id,
|
||||
null as my_vote,
|
||||
null as saved,
|
||||
um.recipient_id,
|
||||
(select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
|
||||
(select local from user_ u where u.id = um.recipient_id) as recipient_local
|
||||
from comment_aggregates_fast ac
|
||||
left join user_mention um on um.comment_id = ac.id
|
||||
;
|
||||
|
||||
|
||||
drop trigger refresh_comment on comment;
|
||||
|
||||
create trigger refresh_comment
|
||||
after insert or update or delete
|
||||
on comment
|
||||
for each row
|
||||
execute procedure refresh_comment();
|
||||
|
||||
-- Sample select
|
||||
-- select * from comment_fast_view where content = 'test_comment' and user_id is null;
|
||||
-- Sample insert
|
||||
-- insert into comment(creator_id, post_id, content) values (2, 2, 'test_comment');
|
||||
-- Sample delete
|
||||
-- delete from comment where content like 'test_comment';
|
||||
-- Sample update
|
||||
-- update comment set removed = true where content like 'test_comment';
|
||||
create or replace function refresh_comment()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
IF (TG_OP = 'DELETE') THEN
|
||||
delete from comment_aggregates_fast where id = OLD.id;
|
||||
|
||||
-- Update community number of comments
|
||||
update community_aggregates_fast as caf
|
||||
set number_of_comments = number_of_comments - 1
|
||||
from post as p
|
||||
where caf.id = p.community_id and p.id = OLD.post_id;
|
||||
|
||||
ELSIF (TG_OP = 'UPDATE') THEN
|
||||
delete from comment_aggregates_fast where id = OLD.id;
|
||||
insert into comment_aggregates_fast select * from comment_aggregates_view where id = NEW.id;
|
||||
ELSIF (TG_OP = 'INSERT') THEN
|
||||
insert into comment_aggregates_fast select * from comment_aggregates_view where id = NEW.id;
|
||||
|
||||
-- Update user view due to comment count
|
||||
update user_fast
|
||||
set number_of_comments = number_of_comments + 1
|
||||
where id = NEW.creator_id;
|
||||
|
||||
-- Update post view due to comment count, new comment activity time, but only on new posts
|
||||
-- TODO this could be done more efficiently
|
||||
delete from post_aggregates_fast where id = NEW.post_id;
|
||||
insert into post_aggregates_fast select * from post_aggregates_view where id = NEW.post_id;
|
||||
|
||||
-- Force the hot rank as zero on week-older posts
|
||||
update post_aggregates_fast as paf
|
||||
set hot_rank = 0
|
||||
where paf.id = NEW.post_id and (paf.published < ('now'::timestamp - '1 week'::interval));
|
||||
|
||||
-- Update community number of comments
|
||||
update community_aggregates_fast as caf
|
||||
set number_of_comments = number_of_comments + 1
|
||||
from post as p
|
||||
where caf.id = p.community_id and p.id = NEW.post_id;
|
||||
|
||||
END IF;
|
||||
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
|
||||
-- post_like
|
||||
-- select id, score, my_vote from post_fast_view where id = 29 and user_id = 4;
|
||||
-- Sample insert
|
||||
-- insert into post_like(user_id, post_id, score) values (4, 29, 1);
|
||||
-- Sample delete
|
||||
-- delete from post_like where user_id = 4 and post_id = 29;
|
||||
-- Sample update
|
||||
-- update post_like set score = -1 where user_id = 4 and post_id = 29;
|
||||
|
||||
-- TODO test this a LOT
|
||||
create or replace function refresh_post_like()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
IF (TG_OP = 'DELETE') THEN
|
||||
update post_aggregates_fast
|
||||
set score = case
|
||||
when (OLD.score = 1) then score - 1
|
||||
else score + 1 end,
|
||||
upvotes = case
|
||||
when (OLD.score = 1) then upvotes - 1
|
||||
else upvotes end,
|
||||
downvotes = case
|
||||
when (OLD.score = -1) then downvotes - 1
|
||||
else downvotes end
|
||||
where id = OLD.post_id;
|
||||
|
||||
ELSIF (TG_OP = 'INSERT') THEN
|
||||
update post_aggregates_fast
|
||||
set score = case
|
||||
when (NEW.score = 1) then score + 1
|
||||
else score - 1 end,
|
||||
upvotes = case
|
||||
when (NEW.score = 1) then upvotes + 1
|
||||
else upvotes end,
|
||||
downvotes = case
|
||||
when (NEW.score = -1) then downvotes + 1
|
||||
else downvotes end
|
||||
where id = NEW.post_id;
|
||||
END IF;
|
||||
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
drop trigger refresh_post_like on post_like;
|
||||
create trigger refresh_post_like
|
||||
after insert or delete
|
||||
on post_like
|
||||
for each row
|
||||
execute procedure refresh_post_like();
|
||||
|
||||
-- comment_like
|
||||
-- select id, score, my_vote from comment_fast_view where id = 29 and user_id = 4;
|
||||
-- Sample insert
|
||||
-- insert into comment_like(user_id, comment_id, post_id, score) values (4, 29, 51, 1);
|
||||
-- Sample delete
|
||||
-- delete from comment_like where user_id = 4 and comment_id = 29;
|
||||
-- Sample update
|
||||
-- update comment_like set score = -1 where user_id = 4 and comment_id = 29;
|
||||
create or replace function refresh_comment_like()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
-- TODO possibly select from comment_fast to get previous scores, instead of re-fetching the views?
|
||||
IF (TG_OP = 'DELETE') THEN
|
||||
update comment_aggregates_fast
|
||||
set score = case
|
||||
when (OLD.score = 1) then score - 1
|
||||
else score + 1 end,
|
||||
upvotes = case
|
||||
when (OLD.score = 1) then upvotes - 1
|
||||
else upvotes end,
|
||||
downvotes = case
|
||||
when (OLD.score = -1) then downvotes - 1
|
||||
else downvotes end
|
||||
where id = OLD.comment_id;
|
||||
|
||||
ELSIF (TG_OP = 'INSERT') THEN
|
||||
update comment_aggregates_fast
|
||||
set score = case
|
||||
when (NEW.score = 1) then score + 1
|
||||
else score - 1 end,
|
||||
upvotes = case
|
||||
when (NEW.score = 1) then upvotes + 1
|
||||
else upvotes end,
|
||||
downvotes = case
|
||||
when (NEW.score = -1) then downvotes + 1
|
||||
else downvotes end
|
||||
where id = NEW.comment_id;
|
||||
END IF;
|
||||
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
drop trigger refresh_comment_like on comment_like;
|
||||
create trigger refresh_comment_like
|
||||
after insert or delete
|
||||
on comment_like
|
||||
for each row
|
||||
execute procedure refresh_comment_like();
|
||||
|
||||
-- Community user ban
|
||||
|
||||
drop trigger refresh_community_user_ban on community_user_ban;
|
||||
create trigger refresh_community_user_ban
|
||||
after insert or delete -- Note this is missing after update
|
||||
on community_user_ban
|
||||
for each row
|
||||
execute procedure refresh_community_user_ban();
|
||||
|
||||
-- select creator_name, banned_from_community from comment_fast_view where user_id = 4 and content = 'test_before_ban';
|
||||
-- select creator_name, banned_from_community, community_id from comment_aggregates_fast where content = 'test_before_ban';
|
||||
-- Sample insert
|
||||
-- insert into comment(creator_id, post_id, content) values (1198, 341, 'test_before_ban');
|
||||
-- insert into community_user_ban(community_id, user_id) values (2, 1198);
|
||||
-- Sample delete
|
||||
-- delete from community_user_ban where user_id = 1198 and community_id = 2;
|
||||
-- delete from comment where content = 'test_before_ban';
|
||||
-- update comment_aggregates_fast set banned_from_community = false where creator_id = 1198 and community_id = 2;
|
||||
create or replace function refresh_community_user_ban()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
-- TODO possibly select from comment_fast to get previous scores, instead of re-fetching the views?
|
||||
IF (TG_OP = 'DELETE') THEN
|
||||
update comment_aggregates_fast set banned_from_community = false where creator_id = OLD.user_id and community_id = OLD.community_id;
|
||||
update post_aggregates_fast set banned_from_community = false where creator_id = OLD.user_id and community_id = OLD.community_id;
|
||||
ELSIF (TG_OP = 'INSERT') THEN
|
||||
update comment_aggregates_fast set banned_from_community = true where creator_id = NEW.user_id and community_id = NEW.community_id;
|
||||
update post_aggregates_fast set banned_from_community = true where creator_id = NEW.user_id and community_id = NEW.community_id;
|
||||
END IF;
|
||||
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
-- Community follower
|
||||
|
||||
drop trigger refresh_community_follower on community_follower;
|
||||
create trigger refresh_community_follower
|
||||
after insert or delete -- Note this is missing after update
|
||||
on community_follower
|
||||
for each row
|
||||
execute procedure refresh_community_follower();
|
||||
|
||||
create or replace function refresh_community_follower()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
IF (TG_OP = 'DELETE') THEN
|
||||
update community_aggregates_fast set number_of_subscribers = number_of_subscribers - 1 where id = OLD.community_id;
|
||||
ELSIF (TG_OP = 'INSERT') THEN
|
||||
update community_aggregates_fast set number_of_subscribers = number_of_subscribers + 1 where id = NEW.community_id;
|
||||
END IF;
|
||||
|
||||
return null;
|
||||
end $$;
|
29
server/query_testing/generate_explain_reports.sh
vendored
29
server/query_testing/generate_explain_reports.sh
vendored
|
@ -1,31 +1,42 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# You can import these to http://tatiyants.com/pev/#/plans/new
|
||||
|
||||
# Do the views first
|
||||
|
||||
echo "explain (analyze, format json) select * from user_mview" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > user_view.json
|
||||
echo "explain (analyze, format json) select * from user_fast" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > user_fast.json
|
||||
|
||||
echo "explain (analyze, format json) select * from post_mview where user_id is null order by hot_rank desc, published desc" > explain.sql
|
||||
echo "explain (analyze, format json) select * from post_view where user_id is null order by hot_rank desc, published desc" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > post_view.json
|
||||
|
||||
echo "explain (analyze, format json) select * from comment_mview where user_id is null" > explain.sql
|
||||
echo "explain (analyze, format json) select * from post_fast_view where user_id is null order by hot_rank desc, published desc" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > post_fast_view.json
|
||||
|
||||
echo "explain (analyze, format json) select * from comment_view where user_id is null" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > comment_view.json
|
||||
|
||||
echo "explain (analyze, format json) select * from community_mview where user_id is null order by hot_rank desc" > explain.sql
|
||||
echo "explain (analyze, format json) select * from comment_fast_view where user_id is null" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > comment_fast_view.json
|
||||
|
||||
echo "explain (analyze, format json) select * from community_view where user_id is null order by hot_rank desc" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > community_view.json
|
||||
|
||||
echo "explain (analyze, format json) select * from community_fast_view where user_id is null order by hot_rank desc" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > community_fast_view.json
|
||||
|
||||
echo "explain (analyze, format json) select * from site_view limit 1" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > site_view.json
|
||||
|
||||
echo "explain (analyze, format json) select * from reply_view where user_id = 34 and recipient_id = 34" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > reply_view.json
|
||||
echo "explain (analyze, format json) select * from reply_fast_view where user_id = 34 and recipient_id = 34" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > reply_fast_view.json
|
||||
|
||||
echo "explain (analyze, format json) select * from user_mention_view where user_id = 34 and recipient_id = 34" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > user_mention_view.json
|
||||
|
||||
echo "explain (analyze, format json) select * from user_mention_mview where user_id = 34 and recipient_id = 34" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > user_mention_mview.json
|
||||
echo "explain (analyze, format json) select * from user_mention_fast_view where user_id = 34 and recipient_id = 34" > explain.sql
|
||||
psql -qAt -U lemmy -f explain.sql > user_mention_fast_view.json
|
||||
|
||||
grep "Execution Time" *.json
|
||||
|
||||
|
|
|
@ -678,7 +678,8 @@ impl Perform for Oper<AddAdmin> {
|
|||
}
|
||||
|
||||
let added = data.added;
|
||||
let add_admin = move |conn: &'_ _| User_::add_admin(conn, user_id, added);
|
||||
let added_user_id = data.user_id;
|
||||
let add_admin = move |conn: &'_ _| User_::add_admin(conn, added_user_id, added);
|
||||
if blocking(pool, add_admin).await?.is_err() {
|
||||
return Err(APIError::err("couldnt_update_user").into());
|
||||
}
|
||||
|
|
|
@ -179,14 +179,10 @@ fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), LemmyEr
|
|||
.filter(local.eq(true))
|
||||
.load::<PrivateMessage>(conn)?;
|
||||
|
||||
sql_query("alter table private_message disable trigger refresh_private_message").execute(conn)?;
|
||||
|
||||
for cpm in &incorrect_pms {
|
||||
PrivateMessage::update_ap_id(&conn, cpm.id)?;
|
||||
}
|
||||
|
||||
sql_query("alter table private_message enable trigger refresh_private_message").execute(conn)?;
|
||||
|
||||
info!("{} private message rows updated.", incorrect_pms.len());
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// TODO, remove the cross join here, just join to user directly
|
||||
use crate::db::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
|
||||
use diesel::{dsl::*, pg::Pg, result::Error, *};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -39,7 +40,7 @@ table! {
|
|||
}
|
||||
|
||||
table! {
|
||||
comment_mview (id) {
|
||||
comment_fast_view (id) {
|
||||
id -> Int4,
|
||||
creator_id -> Int4,
|
||||
post_id -> Int4,
|
||||
|
@ -76,7 +77,7 @@ table! {
|
|||
#[derive(
|
||||
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
|
||||
)]
|
||||
#[table_name = "comment_view"]
|
||||
#[table_name = "comment_fast_view"]
|
||||
pub struct CommentView {
|
||||
pub id: i32,
|
||||
pub creator_id: i32,
|
||||
|
@ -112,7 +113,7 @@ pub struct CommentView {
|
|||
|
||||
pub struct CommentQueryBuilder<'a> {
|
||||
conn: &'a PgConnection,
|
||||
query: super::comment_view::comment_mview::BoxedQuery<'a, Pg>,
|
||||
query: super::comment_view::comment_fast_view::BoxedQuery<'a, Pg>,
|
||||
listing_type: ListingType,
|
||||
sort: &'a SortType,
|
||||
for_community_id: Option<i32>,
|
||||
|
@ -127,9 +128,9 @@ pub struct CommentQueryBuilder<'a> {
|
|||
|
||||
impl<'a> CommentQueryBuilder<'a> {
|
||||
pub fn create(conn: &'a PgConnection) -> Self {
|
||||
use super::comment_view::comment_mview::dsl::*;
|
||||
use super::comment_view::comment_fast_view::dsl::*;
|
||||
|
||||
let query = comment_mview.into_boxed();
|
||||
let query = comment_fast_view.into_boxed();
|
||||
|
||||
CommentQueryBuilder {
|
||||
conn,
|
||||
|
@ -198,7 +199,7 @@ impl<'a> CommentQueryBuilder<'a> {
|
|||
}
|
||||
|
||||
pub fn list(self) -> Result<Vec<CommentView>, Error> {
|
||||
use super::comment_view::comment_mview::dsl::*;
|
||||
use super::comment_view::comment_fast_view::dsl::*;
|
||||
|
||||
let mut query = self.query;
|
||||
|
||||
|
@ -270,8 +271,8 @@ impl CommentView {
|
|||
from_comment_id: i32,
|
||||
my_user_id: Option<i32>,
|
||||
) -> Result<Self, Error> {
|
||||
use super::comment_view::comment_mview::dsl::*;
|
||||
let mut query = comment_mview.into_boxed();
|
||||
use super::comment_view::comment_fast_view::dsl::*;
|
||||
let mut query = comment_fast_view.into_boxed();
|
||||
|
||||
// The view lets you pass a null user_id, if you're not logged in
|
||||
if let Some(my_user_id) = my_user_id {
|
||||
|
@ -290,7 +291,7 @@ impl CommentView {
|
|||
|
||||
// The faked schema since diesel doesn't do views
|
||||
table! {
|
||||
reply_view (id) {
|
||||
reply_fast_view (id) {
|
||||
id -> Int4,
|
||||
creator_id -> Int4,
|
||||
post_id -> Int4,
|
||||
|
@ -328,7 +329,7 @@ table! {
|
|||
#[derive(
|
||||
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
|
||||
)]
|
||||
#[table_name = "reply_view"]
|
||||
#[table_name = "reply_fast_view"]
|
||||
pub struct ReplyView {
|
||||
pub id: i32,
|
||||
pub creator_id: i32,
|
||||
|
@ -365,7 +366,7 @@ pub struct ReplyView {
|
|||
|
||||
pub struct ReplyQueryBuilder<'a> {
|
||||
conn: &'a PgConnection,
|
||||
query: super::comment_view::reply_view::BoxedQuery<'a, Pg>,
|
||||
query: super::comment_view::reply_fast_view::BoxedQuery<'a, Pg>,
|
||||
for_user_id: i32,
|
||||
sort: &'a SortType,
|
||||
unread_only: bool,
|
||||
|
@ -375,9 +376,9 @@ pub struct ReplyQueryBuilder<'a> {
|
|||
|
||||
impl<'a> ReplyQueryBuilder<'a> {
|
||||
pub fn create(conn: &'a PgConnection, for_user_id: i32) -> Self {
|
||||
use super::comment_view::reply_view::dsl::*;
|
||||
use super::comment_view::reply_fast_view::dsl::*;
|
||||
|
||||
let query = reply_view.into_boxed();
|
||||
let query = reply_fast_view.into_boxed();
|
||||
|
||||
ReplyQueryBuilder {
|
||||
conn,
|
||||
|
@ -411,7 +412,7 @@ impl<'a> ReplyQueryBuilder<'a> {
|
|||
}
|
||||
|
||||
pub fn list(self) -> Result<Vec<ReplyView>, Error> {
|
||||
use super::comment_view::reply_view::dsl::*;
|
||||
use super::comment_view::reply_fast_view::dsl::*;
|
||||
|
||||
let mut query = self.query;
|
||||
|
||||
|
@ -615,8 +616,8 @@ mod tests {
|
|||
upvotes: 1,
|
||||
user_id: Some(inserted_user.id),
|
||||
my_vote: Some(1),
|
||||
subscribed: None,
|
||||
saved: None,
|
||||
subscribed: Some(false),
|
||||
saved: Some(false),
|
||||
ap_id: "http://fake.com".to_string(),
|
||||
local: true,
|
||||
community_actor_id: inserted_community.actor_id.to_owned(),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::community_view::community_mview::BoxedQuery;
|
||||
use super::community_view::community_fast_view::BoxedQuery;
|
||||
use crate::db::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
|
||||
use diesel::{pg::Pg, result::Error, *};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -34,7 +34,7 @@ table! {
|
|||
}
|
||||
|
||||
table! {
|
||||
community_mview (id) {
|
||||
community_fast_view (id) {
|
||||
id -> Int4,
|
||||
name -> Varchar,
|
||||
title -> Varchar,
|
||||
|
@ -114,7 +114,7 @@ table! {
|
|||
#[derive(
|
||||
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
|
||||
)]
|
||||
#[table_name = "community_view"]
|
||||
#[table_name = "community_fast_view"]
|
||||
pub struct CommunityView {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
|
@ -156,9 +156,9 @@ pub struct CommunityQueryBuilder<'a> {
|
|||
|
||||
impl<'a> CommunityQueryBuilder<'a> {
|
||||
pub fn create(conn: &'a PgConnection) -> Self {
|
||||
use super::community_view::community_mview::dsl::*;
|
||||
use super::community_view::community_fast_view::dsl::*;
|
||||
|
||||
let query = community_mview.into_boxed();
|
||||
let query = community_fast_view.into_boxed();
|
||||
|
||||
CommunityQueryBuilder {
|
||||
conn,
|
||||
|
@ -203,7 +203,7 @@ impl<'a> CommunityQueryBuilder<'a> {
|
|||
}
|
||||
|
||||
pub fn list(self) -> Result<Vec<CommunityView>, Error> {
|
||||
use super::community_view::community_mview::dsl::*;
|
||||
use super::community_view::community_fast_view::dsl::*;
|
||||
|
||||
let mut query = self.query;
|
||||
|
||||
|
@ -259,9 +259,9 @@ impl CommunityView {
|
|||
from_community_id: i32,
|
||||
from_user_id: Option<i32>,
|
||||
) -> Result<Self, Error> {
|
||||
use super::community_view::community_mview::dsl::*;
|
||||
use super::community_view::community_fast_view::dsl::*;
|
||||
|
||||
let mut query = community_mview.into_boxed();
|
||||
let mut query = community_fast_view.into_boxed();
|
||||
|
||||
query = query.filter(id.eq(from_community_id));
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::post_view::post_mview::BoxedQuery;
|
||||
use super::post_view::post_fast_view::BoxedQuery;
|
||||
use crate::db::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
|
||||
use diesel::{dsl::*, pg::Pg, result::Error, *};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -25,12 +25,12 @@ table! {
|
|||
thumbnail_url -> Nullable<Text>,
|
||||
ap_id -> Text,
|
||||
local -> Bool,
|
||||
banned -> Bool,
|
||||
banned_from_community -> Bool,
|
||||
creator_actor_id -> Text,
|
||||
creator_local -> Bool,
|
||||
creator_name -> Varchar,
|
||||
creator_avatar -> Nullable<Text>,
|
||||
banned -> Bool,
|
||||
banned_from_community -> Bool,
|
||||
community_actor_id -> Text,
|
||||
community_local -> Bool,
|
||||
community_name -> Varchar,
|
||||
|
@ -52,7 +52,7 @@ table! {
|
|||
}
|
||||
|
||||
table! {
|
||||
post_mview (id) {
|
||||
post_fast_view (id) {
|
||||
id -> Int4,
|
||||
name -> Varchar,
|
||||
url -> Nullable<Text>,
|
||||
|
@ -72,12 +72,12 @@ table! {
|
|||
thumbnail_url -> Nullable<Text>,
|
||||
ap_id -> Text,
|
||||
local -> Bool,
|
||||
banned -> Bool,
|
||||
banned_from_community -> Bool,
|
||||
creator_actor_id -> Text,
|
||||
creator_local -> Bool,
|
||||
creator_name -> Varchar,
|
||||
creator_avatar -> Nullable<Text>,
|
||||
banned -> Bool,
|
||||
banned_from_community -> Bool,
|
||||
community_actor_id -> Text,
|
||||
community_local -> Bool,
|
||||
community_name -> Varchar,
|
||||
|
@ -101,7 +101,7 @@ table! {
|
|||
#[derive(
|
||||
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
|
||||
)]
|
||||
#[table_name = "post_view"]
|
||||
#[table_name = "post_fast_view"]
|
||||
pub struct PostView {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
|
@ -122,12 +122,12 @@ pub struct PostView {
|
|||
pub thumbnail_url: Option<String>,
|
||||
pub ap_id: String,
|
||||
pub local: bool,
|
||||
pub banned: bool,
|
||||
pub banned_from_community: bool,
|
||||
pub creator_actor_id: String,
|
||||
pub creator_local: bool,
|
||||
pub creator_name: String,
|
||||
pub creator_avatar: Option<String>,
|
||||
pub banned: bool,
|
||||
pub banned_from_community: bool,
|
||||
pub community_actor_id: String,
|
||||
pub community_local: bool,
|
||||
pub community_name: String,
|
||||
|
@ -166,9 +166,9 @@ pub struct PostQueryBuilder<'a> {
|
|||
|
||||
impl<'a> PostQueryBuilder<'a> {
|
||||
pub fn create(conn: &'a PgConnection) -> Self {
|
||||
use super::post_view::post_mview::dsl::*;
|
||||
use super::post_view::post_fast_view::dsl::*;
|
||||
|
||||
let query = post_mview.into_boxed();
|
||||
let query = post_fast_view.into_boxed();
|
||||
|
||||
PostQueryBuilder {
|
||||
conn,
|
||||
|
@ -249,7 +249,7 @@ impl<'a> PostQueryBuilder<'a> {
|
|||
}
|
||||
|
||||
pub fn list(self) -> Result<Vec<PostView>, Error> {
|
||||
use super::post_view::post_mview::dsl::*;
|
||||
use super::post_view::post_fast_view::dsl::*;
|
||||
|
||||
let mut query = self.query;
|
||||
|
||||
|
@ -345,10 +345,10 @@ impl PostView {
|
|||
from_post_id: i32,
|
||||
my_user_id: Option<i32>,
|
||||
) -> Result<Self, Error> {
|
||||
use super::post_view::post_mview::dsl::*;
|
||||
use super::post_view::post_fast_view::dsl::*;
|
||||
use diesel::prelude::*;
|
||||
|
||||
let mut query = post_mview.into_boxed();
|
||||
let mut query = post_fast_view.into_boxed();
|
||||
|
||||
query = query.filter(id.eq(from_post_id));
|
||||
|
||||
|
@ -470,6 +470,25 @@ mod tests {
|
|||
score: 1,
|
||||
};
|
||||
|
||||
let read_post_listings_with_user = PostQueryBuilder::create(&conn)
|
||||
.listing_type(ListingType::Community)
|
||||
.sort(&SortType::New)
|
||||
.for_community_id(inserted_community.id)
|
||||
.my_user_id(inserted_user.id)
|
||||
.list()
|
||||
.unwrap();
|
||||
|
||||
let read_post_listings_no_user = PostQueryBuilder::create(&conn)
|
||||
.listing_type(ListingType::Community)
|
||||
.sort(&SortType::New)
|
||||
.for_community_id(inserted_community.id)
|
||||
.list()
|
||||
.unwrap();
|
||||
|
||||
let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap();
|
||||
let read_post_listing_with_user =
|
||||
PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
|
||||
|
||||
// the non user version
|
||||
let expected_post_listing_no_user = PostView {
|
||||
user_id: None,
|
||||
|
@ -496,7 +515,7 @@ mod tests {
|
|||
score: 1,
|
||||
upvotes: 1,
|
||||
downvotes: 0,
|
||||
hot_rank: 1728,
|
||||
hot_rank: read_post_listing_no_user.hot_rank,
|
||||
published: inserted_post.published,
|
||||
newest_activity_time: inserted_post.published,
|
||||
updated: None,
|
||||
|
@ -541,13 +560,13 @@ mod tests {
|
|||
score: 1,
|
||||
upvotes: 1,
|
||||
downvotes: 0,
|
||||
hot_rank: 1728,
|
||||
hot_rank: read_post_listing_with_user.hot_rank,
|
||||
published: inserted_post.published,
|
||||
newest_activity_time: inserted_post.published,
|
||||
updated: None,
|
||||
subscribed: None,
|
||||
read: None,
|
||||
saved: None,
|
||||
subscribed: Some(false),
|
||||
read: Some(false),
|
||||
saved: Some(false),
|
||||
nsfw: false,
|
||||
embed_title: None,
|
||||
embed_description: None,
|
||||
|
@ -561,25 +580,6 @@ mod tests {
|
|||
community_local: true,
|
||||
};
|
||||
|
||||
let read_post_listings_with_user = PostQueryBuilder::create(&conn)
|
||||
.listing_type(ListingType::Community)
|
||||
.sort(&SortType::New)
|
||||
.for_community_id(inserted_community.id)
|
||||
.my_user_id(inserted_user.id)
|
||||
.list()
|
||||
.unwrap();
|
||||
|
||||
let read_post_listings_no_user = PostQueryBuilder::create(&conn)
|
||||
.listing_type(ListingType::Community)
|
||||
.sort(&SortType::New)
|
||||
.for_community_id(inserted_community.id)
|
||||
.list()
|
||||
.unwrap();
|
||||
|
||||
let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap();
|
||||
let read_post_listing_with_user =
|
||||
PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
|
||||
|
||||
let like_removed = PostLike::remove(&conn, &post_like_form).unwrap();
|
||||
let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
|
||||
Community::delete(&conn, inserted_community.id).unwrap();
|
||||
|
|
|
@ -26,29 +26,6 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
private_message_mview (id) {
|
||||
id -> Int4,
|
||||
creator_id -> Int4,
|
||||
recipient_id -> Int4,
|
||||
content -> Text,
|
||||
deleted -> Bool,
|
||||
read -> Bool,
|
||||
published -> Timestamp,
|
||||
updated -> Nullable<Timestamp>,
|
||||
ap_id -> Text,
|
||||
local -> Bool,
|
||||
creator_name -> Varchar,
|
||||
creator_avatar -> Nullable<Text>,
|
||||
creator_actor_id -> Text,
|
||||
creator_local -> Bool,
|
||||
recipient_name -> Varchar,
|
||||
recipient_avatar -> Nullable<Text>,
|
||||
recipient_actor_id -> Text,
|
||||
recipient_local -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
|
||||
)]
|
||||
|
@ -76,7 +53,7 @@ pub struct PrivateMessageView {
|
|||
|
||||
pub struct PrivateMessageQueryBuilder<'a> {
|
||||
conn: &'a PgConnection,
|
||||
query: super::private_message_view::private_message_mview::BoxedQuery<'a, Pg>,
|
||||
query: super::private_message_view::private_message_view::BoxedQuery<'a, Pg>,
|
||||
for_recipient_id: i32,
|
||||
unread_only: bool,
|
||||
page: Option<i64>,
|
||||
|
@ -85,9 +62,9 @@ pub struct PrivateMessageQueryBuilder<'a> {
|
|||
|
||||
impl<'a> PrivateMessageQueryBuilder<'a> {
|
||||
pub fn create(conn: &'a PgConnection, for_recipient_id: i32) -> Self {
|
||||
use super::private_message_view::private_message_mview::dsl::*;
|
||||
use super::private_message_view::private_message_view::dsl::*;
|
||||
|
||||
let query = private_message_mview.into_boxed();
|
||||
let query = private_message_view.into_boxed();
|
||||
|
||||
PrivateMessageQueryBuilder {
|
||||
conn,
|
||||
|
@ -115,7 +92,7 @@ impl<'a> PrivateMessageQueryBuilder<'a> {
|
|||
}
|
||||
|
||||
pub fn list(self) -> Result<Vec<PrivateMessageView>, Error> {
|
||||
use super::private_message_view::private_message_mview::dsl::*;
|
||||
use super::private_message_view::private_message_view::dsl::*;
|
||||
|
||||
let mut query = self.query.filter(deleted.eq(false));
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ table! {
|
|||
}
|
||||
|
||||
table! {
|
||||
user_mention_mview (id) {
|
||||
user_mention_fast_view (id) {
|
||||
id -> Int4,
|
||||
user_mention_id -> Int4,
|
||||
creator_id -> Int4,
|
||||
|
@ -78,7 +78,7 @@ table! {
|
|||
#[derive(
|
||||
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
|
||||
)]
|
||||
#[table_name = "user_mention_view"]
|
||||
#[table_name = "user_mention_fast_view"]
|
||||
pub struct UserMentionView {
|
||||
pub id: i32,
|
||||
pub user_mention_id: i32,
|
||||
|
@ -115,7 +115,7 @@ pub struct UserMentionView {
|
|||
|
||||
pub struct UserMentionQueryBuilder<'a> {
|
||||
conn: &'a PgConnection,
|
||||
query: super::user_mention_view::user_mention_mview::BoxedQuery<'a, Pg>,
|
||||
query: super::user_mention_view::user_mention_fast_view::BoxedQuery<'a, Pg>,
|
||||
for_user_id: i32,
|
||||
sort: &'a SortType,
|
||||
unread_only: bool,
|
||||
|
@ -125,9 +125,9 @@ pub struct UserMentionQueryBuilder<'a> {
|
|||
|
||||
impl<'a> UserMentionQueryBuilder<'a> {
|
||||
pub fn create(conn: &'a PgConnection, for_user_id: i32) -> Self {
|
||||
use super::user_mention_view::user_mention_mview::dsl::*;
|
||||
use super::user_mention_view::user_mention_fast_view::dsl::*;
|
||||
|
||||
let query = user_mention_mview.into_boxed();
|
||||
let query = user_mention_fast_view.into_boxed();
|
||||
|
||||
UserMentionQueryBuilder {
|
||||
conn,
|
||||
|
@ -161,7 +161,7 @@ impl<'a> UserMentionQueryBuilder<'a> {
|
|||
}
|
||||
|
||||
pub fn list(self) -> Result<Vec<UserMentionView>, Error> {
|
||||
use super::user_mention_view::user_mention_mview::dsl::*;
|
||||
use super::user_mention_view::user_mention_fast_view::dsl::*;
|
||||
|
||||
let mut query = self.query;
|
||||
|
||||
|
@ -208,9 +208,9 @@ impl UserMentionView {
|
|||
from_user_mention_id: i32,
|
||||
from_recipient_id: i32,
|
||||
) -> Result<Self, Error> {
|
||||
use super::user_mention_view::user_mention_view::dsl::*;
|
||||
use super::user_mention_view::user_mention_fast_view::dsl::*;
|
||||
|
||||
user_mention_view
|
||||
user_mention_fast_view
|
||||
.filter(user_mention_id.eq(from_user_mention_id))
|
||||
.filter(user_id.eq(from_recipient_id))
|
||||
.first::<Self>(conn)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::user_view::user_mview::BoxedQuery;
|
||||
use super::user_view::user_fast::BoxedQuery;
|
||||
use crate::db::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
|
||||
use diesel::{dsl::*, pg::Pg, result::Error, *};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -26,7 +26,7 @@ table! {
|
|||
}
|
||||
|
||||
table! {
|
||||
user_mview (id) {
|
||||
user_fast (id) {
|
||||
id -> Int4,
|
||||
actor_id -> Text,
|
||||
name -> Varchar,
|
||||
|
@ -50,7 +50,7 @@ table! {
|
|||
#[derive(
|
||||
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
|
||||
)]
|
||||
#[table_name = "user_view"]
|
||||
#[table_name = "user_fast"]
|
||||
pub struct UserView {
|
||||
pub id: i32,
|
||||
pub actor_id: String,
|
||||
|
@ -81,9 +81,9 @@ pub struct UserQueryBuilder<'a> {
|
|||
|
||||
impl<'a> UserQueryBuilder<'a> {
|
||||
pub fn create(conn: &'a PgConnection) -> Self {
|
||||
use super::user_view::user_mview::dsl::*;
|
||||
use super::user_view::user_fast::dsl::*;
|
||||
|
||||
let query = user_mview.into_boxed();
|
||||
let query = user_fast.into_boxed();
|
||||
|
||||
UserQueryBuilder {
|
||||
conn,
|
||||
|
@ -100,7 +100,7 @@ impl<'a> UserQueryBuilder<'a> {
|
|||
}
|
||||
|
||||
pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
|
||||
use super::user_view::user_mview::dsl::*;
|
||||
use super::user_view::user_fast::dsl::*;
|
||||
if let Some(search_term) = search_term.get_optional() {
|
||||
self.query = self.query.filter(name.ilike(fuzzy_search(&search_term)));
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ impl<'a> UserQueryBuilder<'a> {
|
|||
}
|
||||
|
||||
pub fn list(self) -> Result<Vec<UserView>, Error> {
|
||||
use super::user_view::user_mview::dsl::*;
|
||||
use super::user_view::user_fast::dsl::*;
|
||||
|
||||
let mut query = self.query;
|
||||
|
||||
|
@ -151,17 +151,17 @@ impl<'a> UserQueryBuilder<'a> {
|
|||
|
||||
impl UserView {
|
||||
pub fn read(conn: &PgConnection, from_user_id: i32) -> Result<Self, Error> {
|
||||
use super::user_view::user_mview::dsl::*;
|
||||
user_mview.find(from_user_id).first::<Self>(conn)
|
||||
use super::user_view::user_fast::dsl::*;
|
||||
user_fast.find(from_user_id).first::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn admins(conn: &PgConnection) -> Result<Vec<Self>, Error> {
|
||||
use super::user_view::user_mview::dsl::*;
|
||||
user_mview.filter(admin.eq(true)).load::<Self>(conn)
|
||||
use super::user_view::user_fast::dsl::*;
|
||||
user_fast.filter(admin.eq(true)).load::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn banned(conn: &PgConnection) -> Result<Vec<Self>, Error> {
|
||||
use super::user_view::user_mview::dsl::*;
|
||||
user_mview.filter(banned.eq(true)).load::<Self>(conn)
|
||||
use super::user_view::user_fast::dsl::*;
|
||||
user_fast.filter(banned.eq(true)).load::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -325,7 +325,7 @@ pub fn markdown_to_html(text: &str) -> String {
|
|||
|
||||
pub fn get_ip(conn_info: &ConnectionInfo) -> String {
|
||||
conn_info
|
||||
.remote_addr()
|
||||
.realip_remote_addr()
|
||||
.unwrap_or("127.0.0.1:12345")
|
||||
.split(':')
|
||||
.next()
|
||||
|
|
|
@ -33,6 +33,37 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
comment_aggregates_fast (id) {
|
||||
id -> Int4,
|
||||
creator_id -> Nullable<Int4>,
|
||||
post_id -> Nullable<Int4>,
|
||||
parent_id -> Nullable<Int4>,
|
||||
content -> Nullable<Text>,
|
||||
removed -> Nullable<Bool>,
|
||||
read -> Nullable<Bool>,
|
||||
published -> Nullable<Timestamp>,
|
||||
updated -> Nullable<Timestamp>,
|
||||
deleted -> Nullable<Bool>,
|
||||
ap_id -> Nullable<Varchar>,
|
||||
local -> Nullable<Bool>,
|
||||
community_id -> Nullable<Int4>,
|
||||
community_actor_id -> Nullable<Varchar>,
|
||||
community_local -> Nullable<Bool>,
|
||||
community_name -> Nullable<Varchar>,
|
||||
banned -> Nullable<Bool>,
|
||||
banned_from_community -> Nullable<Bool>,
|
||||
creator_actor_id -> Nullable<Varchar>,
|
||||
creator_local -> Nullable<Bool>,
|
||||
creator_name -> Nullable<Varchar>,
|
||||
creator_avatar -> Nullable<Text>,
|
||||
score -> Nullable<Int8>,
|
||||
upvotes -> Nullable<Int8>,
|
||||
downvotes -> Nullable<Int8>,
|
||||
hot_rank -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
comment_like (id) {
|
||||
id -> Int4,
|
||||
|
@ -74,6 +105,34 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
community_aggregates_fast (id) {
|
||||
id -> Int4,
|
||||
name -> Nullable<Varchar>,
|
||||
title -> Nullable<Varchar>,
|
||||
description -> Nullable<Text>,
|
||||
category_id -> Nullable<Int4>,
|
||||
creator_id -> Nullable<Int4>,
|
||||
removed -> Nullable<Bool>,
|
||||
published -> Nullable<Timestamp>,
|
||||
updated -> Nullable<Timestamp>,
|
||||
deleted -> Nullable<Bool>,
|
||||
nsfw -> Nullable<Bool>,
|
||||
actor_id -> Nullable<Varchar>,
|
||||
local -> Nullable<Bool>,
|
||||
last_refreshed_at -> Nullable<Timestamp>,
|
||||
creator_actor_id -> Nullable<Varchar>,
|
||||
creator_local -> Nullable<Bool>,
|
||||
creator_name -> Nullable<Varchar>,
|
||||
creator_avatar -> Nullable<Text>,
|
||||
category_name -> Nullable<Varchar>,
|
||||
number_of_subscribers -> Nullable<Int8>,
|
||||
number_of_posts -> Nullable<Int8>,
|
||||
number_of_comments -> Nullable<Int8>,
|
||||
hot_rank -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
community_follower (id) {
|
||||
id -> Int4,
|
||||
|
@ -234,6 +293,48 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
post_aggregates_fast (id) {
|
||||
id -> Int4,
|
||||
name -> Nullable<Varchar>,
|
||||
url -> Nullable<Text>,
|
||||
body -> Nullable<Text>,
|
||||
creator_id -> Nullable<Int4>,
|
||||
community_id -> Nullable<Int4>,
|
||||
removed -> Nullable<Bool>,
|
||||
locked -> Nullable<Bool>,
|
||||
published -> Nullable<Timestamp>,
|
||||
updated -> Nullable<Timestamp>,
|
||||
deleted -> Nullable<Bool>,
|
||||
nsfw -> Nullable<Bool>,
|
||||
stickied -> Nullable<Bool>,
|
||||
embed_title -> Nullable<Text>,
|
||||
embed_description -> Nullable<Text>,
|
||||
embed_html -> Nullable<Text>,
|
||||
thumbnail_url -> Nullable<Text>,
|
||||
ap_id -> Nullable<Varchar>,
|
||||
local -> Nullable<Bool>,
|
||||
creator_actor_id -> Nullable<Varchar>,
|
||||
creator_local -> Nullable<Bool>,
|
||||
creator_name -> Nullable<Varchar>,
|
||||
creator_avatar -> Nullable<Text>,
|
||||
banned -> Nullable<Bool>,
|
||||
banned_from_community -> Nullable<Bool>,
|
||||
community_actor_id -> Nullable<Varchar>,
|
||||
community_local -> Nullable<Bool>,
|
||||
community_name -> Nullable<Varchar>,
|
||||
community_removed -> Nullable<Bool>,
|
||||
community_deleted -> Nullable<Bool>,
|
||||
community_nsfw -> Nullable<Bool>,
|
||||
number_of_comments -> Nullable<Int8>,
|
||||
score -> Nullable<Int8>,
|
||||
upvotes -> Nullable<Int8>,
|
||||
downvotes -> Nullable<Int8>,
|
||||
hot_rank -> Nullable<Int4>,
|
||||
newest_activity_time -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
post_like (id) {
|
||||
id -> Int4,
|
||||
|
@ -328,6 +429,28 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
user_fast (id) {
|
||||
id -> Int4,
|
||||
actor_id -> Nullable<Varchar>,
|
||||
name -> Nullable<Varchar>,
|
||||
avatar -> Nullable<Text>,
|
||||
email -> Nullable<Text>,
|
||||
matrix_user_id -> Nullable<Text>,
|
||||
bio -> Nullable<Text>,
|
||||
local -> Nullable<Bool>,
|
||||
admin -> Nullable<Bool>,
|
||||
banned -> Nullable<Bool>,
|
||||
show_avatars -> Nullable<Bool>,
|
||||
send_notifications_to_email -> Nullable<Bool>,
|
||||
published -> Nullable<Timestamp>,
|
||||
number_of_posts -> Nullable<Int8>,
|
||||
post_score -> Nullable<Int8>,
|
||||
number_of_comments -> Nullable<Int8>,
|
||||
comment_score -> Nullable<Int8>,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
user_mention (id) {
|
||||
id -> Int4,
|
||||
|
@ -384,9 +507,11 @@ allow_tables_to_appear_in_same_query!(
|
|||
activity,
|
||||
category,
|
||||
comment,
|
||||
comment_aggregates_fast,
|
||||
comment_like,
|
||||
comment_saved,
|
||||
community,
|
||||
community_aggregates_fast,
|
||||
community_follower,
|
||||
community_moderator,
|
||||
community_user_ban,
|
||||
|
@ -401,6 +526,7 @@ allow_tables_to_appear_in_same_query!(
|
|||
mod_sticky_post,
|
||||
password_reset_request,
|
||||
post,
|
||||
post_aggregates_fast,
|
||||
post_like,
|
||||
post_read,
|
||||
post_saved,
|
||||
|
@ -408,5 +534,6 @@ allow_tables_to_appear_in_same_query!(
|
|||
site,
|
||||
user_,
|
||||
user_ban,
|
||||
user_fast,
|
||||
user_mention,
|
||||
);
|
||||
|
|
|
@ -1 +1 @@
|
|||
pub const VERSION: &str = "v0.7.11";
|
||||
pub const VERSION: &str = "v0.7.13";
|
||||
|
|
4
ui/src/components/comment-node.tsx
vendored
4
ui/src/components/comment-node.tsx
vendored
|
@ -73,6 +73,7 @@ interface CommentNodeProps {
|
|||
showCommunity?: boolean;
|
||||
sort?: CommentSortType;
|
||||
sortType?: SortType;
|
||||
enableDownvotes: boolean;
|
||||
}
|
||||
|
||||
export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||
|
@ -279,7 +280,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
<span class="ml-1">{this.state.upvotes}</span>
|
||||
)}
|
||||
</button>
|
||||
{WebSocketService.Instance.site.enable_downvotes && (
|
||||
{this.props.enableDownvotes && (
|
||||
<button
|
||||
className={`btn btn-link btn-animate ${
|
||||
this.state.my_vote == -1
|
||||
|
@ -703,6 +704,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
postCreatorId={this.props.postCreatorId}
|
||||
sort={this.props.sort}
|
||||
sortType={this.props.sortType}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
/>
|
||||
)}
|
||||
{/* A collapsed clearfix */}
|
||||
|
|
2
ui/src/components/comment-nodes.tsx
vendored
2
ui/src/components/comment-nodes.tsx
vendored
|
@ -24,6 +24,7 @@ interface CommentNodesProps {
|
|||
showCommunity?: boolean;
|
||||
sort?: CommentSortType;
|
||||
sortType?: SortType;
|
||||
enableDownvotes: boolean;
|
||||
}
|
||||
|
||||
export class CommentNodes extends Component<
|
||||
|
@ -52,6 +53,7 @@ export class CommentNodes extends Component<
|
|||
showCommunity={this.props.showCommunity}
|
||||
sort={this.props.sort}
|
||||
sortType={this.props.sortType}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
12
ui/src/components/communities.tsx
vendored
12
ui/src/components/communities.tsx
vendored
|
@ -1,5 +1,4 @@
|
|||
import { Component, linkEvent } from 'inferno';
|
||||
import { Link } from 'inferno-router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||
import {
|
||||
|
@ -11,6 +10,7 @@ import {
|
|||
ListCommunitiesForm,
|
||||
SortType,
|
||||
WebSocketJsonResponse,
|
||||
GetSiteResponse,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService } from '../services';
|
||||
import { wsJsonToRes, toast } from '../utils';
|
||||
|
@ -47,6 +47,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
);
|
||||
|
||||
this.refetch();
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
getPageFromProps(props: any): number {
|
||||
|
@ -57,12 +58,6 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = `${i18n.t('communities')} - ${
|
||||
WebSocketService.Instance.site.name
|
||||
}`;
|
||||
}
|
||||
|
||||
// Necessary for back button for some reason
|
||||
componentWillReceiveProps(nextProps: any) {
|
||||
if (nextProps.history.action == 'POP') {
|
||||
|
@ -244,6 +239,9 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
found.subscribed = data.community.subscribed;
|
||||
found.number_of_subscribers = data.community.number_of_subscribers;
|
||||
this.setState(this.state);
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
document.title = `${i18n.t('communities')} - ${data.site.name}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
11
ui/src/components/community-form.tsx
vendored
11
ui/src/components/community-form.tsx
vendored
|
@ -8,7 +8,6 @@ import {
|
|||
Category,
|
||||
ListCategoriesResponse,
|
||||
CommunityResponse,
|
||||
GetSiteResponse,
|
||||
WebSocketJsonResponse,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService } from '../services';
|
||||
|
@ -30,13 +29,13 @@ interface CommunityFormProps {
|
|||
onCancel?(): any;
|
||||
onCreate?(community: Community): any;
|
||||
onEdit?(community: Community): any;
|
||||
enableNsfw: boolean;
|
||||
}
|
||||
|
||||
interface CommunityFormState {
|
||||
communityForm: CommunityFormI;
|
||||
categories: Array<Category>;
|
||||
loading: boolean;
|
||||
enable_nsfw: boolean;
|
||||
}
|
||||
|
||||
export class CommunityForm extends Component<
|
||||
|
@ -56,7 +55,6 @@ export class CommunityForm extends Component<
|
|||
},
|
||||
categories: [],
|
||||
loading: false,
|
||||
enable_nsfw: null,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
|
@ -86,7 +84,6 @@ export class CommunityForm extends Component<
|
|||
);
|
||||
|
||||
WebSocketService.Instance.listCategories();
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -187,7 +184,7 @@ export class CommunityForm extends Component<
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.enable_nsfw && (
|
||||
{this.props.enableNsfw && (
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
|
@ -303,10 +300,6 @@ export class CommunityForm extends Component<
|
|||
let data = res.data as CommunityResponse;
|
||||
this.state.loading = false;
|
||||
this.props.onEdit(data.community);
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
this.state.enable_nsfw = data.site.enable_nsfw;
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
28
ui/src/components/community.tsx
vendored
28
ui/src/components/community.tsx
vendored
|
@ -23,6 +23,8 @@ import {
|
|||
GetCommentsResponse,
|
||||
CommentResponse,
|
||||
WebSocketJsonResponse,
|
||||
GetSiteResponse,
|
||||
Site,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService } from '../services';
|
||||
import { PostListings } from './post-listings';
|
||||
|
@ -60,6 +62,7 @@ interface State {
|
|||
dataType: DataType;
|
||||
sort: SortType;
|
||||
page: number;
|
||||
site: Site;
|
||||
}
|
||||
|
||||
export class Community extends Component<any, State> {
|
||||
|
@ -97,6 +100,20 @@ export class Community extends Component<any, State> {
|
|||
dataType: getDataTypeFromProps(this.props),
|
||||
sort: getSortTypeFromProps(this.props),
|
||||
page: getPageFromProps(this.props),
|
||||
site: {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
creator_id: undefined,
|
||||
published: undefined,
|
||||
creator_name: undefined,
|
||||
number_of_users: undefined,
|
||||
number_of_posts: undefined,
|
||||
number_of_comments: undefined,
|
||||
number_of_communities: undefined,
|
||||
enable_downvotes: undefined,
|
||||
open_registration: undefined,
|
||||
enable_nsfw: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
|
@ -119,6 +136,7 @@ export class Community extends Component<any, State> {
|
|||
name: this.state.communityName ? this.state.communityName : null,
|
||||
};
|
||||
WebSocketService.Instance.getCommunity(form);
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -174,6 +192,7 @@ export class Community extends Component<any, State> {
|
|||
moderators={this.state.moderators}
|
||||
admins={this.state.admins}
|
||||
online={this.state.online}
|
||||
enableNsfw={this.state.site.enable_nsfw}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -188,6 +207,8 @@ export class Community extends Component<any, State> {
|
|||
posts={this.state.posts}
|
||||
removeDuplicates
|
||||
sort={this.state.sort}
|
||||
enableDownvotes={this.state.site.enable_downvotes}
|
||||
enableNsfw={this.state.site.enable_nsfw}
|
||||
/>
|
||||
) : (
|
||||
<CommentNodes
|
||||
|
@ -195,6 +216,7 @@ export class Community extends Component<any, State> {
|
|||
noIndent
|
||||
sortType={this.state.sort}
|
||||
showContext
|
||||
enableDownvotes={this.state.site.enable_downvotes}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -331,7 +353,7 @@ export class Community extends Component<any, State> {
|
|||
this.state.moderators = data.moderators;
|
||||
this.state.admins = data.admins;
|
||||
this.state.online = data.online;
|
||||
document.title = `/c/${this.state.community.name} - ${WebSocketService.Instance.site.name}`;
|
||||
document.title = `/c/${this.state.community.name} - ${this.state.site.name}`;
|
||||
this.setState(this.state);
|
||||
this.fetchData();
|
||||
} else if (res.op == UserOperation.EditCommunity) {
|
||||
|
@ -399,6 +421,10 @@ export class Community extends Component<any, State> {
|
|||
let data = res.data as CommentResponse;
|
||||
createCommentLikeRes(data, this.state.comments);
|
||||
this.setState(this.state);
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
this.state.site = data.site;
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
56
ui/src/components/create-community.tsx
vendored
56
ui/src/components/create-community.tsx
vendored
|
@ -1,19 +1,44 @@
|
|||
import { Component } from 'inferno';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||
import { CommunityForm } from './community-form';
|
||||
import { Community } from '../interfaces';
|
||||
import {
|
||||
Community,
|
||||
UserOperation,
|
||||
WebSocketJsonResponse,
|
||||
GetSiteResponse,
|
||||
} from '../interfaces';
|
||||
import { toast, wsJsonToRes } from '../utils';
|
||||
import { WebSocketService } from '../services';
|
||||
import { i18n } from '../i18next';
|
||||
|
||||
export class CreateCommunity extends Component<any, any> {
|
||||
interface CreateCommunityState {
|
||||
enableNsfw: boolean;
|
||||
}
|
||||
|
||||
export class CreateCommunity extends Component<any, CreateCommunityState> {
|
||||
private subscription: Subscription;
|
||||
private emptyState: CreateCommunityState = {
|
||||
enableNsfw: null,
|
||||
};
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
this.handleCommunityCreate = this.handleCommunityCreate.bind(this);
|
||||
this.state = this.emptyState;
|
||||
|
||||
this.subscription = WebSocketService.Instance.subject
|
||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||
.subscribe(
|
||||
msg => this.parseMessage(msg),
|
||||
err => console.error(err),
|
||||
() => console.log('complete')
|
||||
);
|
||||
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = `${i18n.t('create_community')} - ${
|
||||
WebSocketService.Instance.site.name
|
||||
}`;
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -22,7 +47,10 @@ export class CreateCommunity extends Component<any, any> {
|
|||
<div class="row">
|
||||
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
|
||||
<h5>{i18n.t('create_community')}</h5>
|
||||
<CommunityForm onCreate={this.handleCommunityCreate} />
|
||||
<CommunityForm
|
||||
onCreate={this.handleCommunityCreate}
|
||||
enableNsfw={this.state.enableNsfw}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -32,4 +60,18 @@ export class CreateCommunity extends Component<any, any> {
|
|||
handleCommunityCreate(community: Community) {
|
||||
this.props.history.push(`/c/${community.name}`);
|
||||
}
|
||||
|
||||
parseMessage(msg: WebSocketJsonResponse) {
|
||||
console.log(msg);
|
||||
let res = wsJsonToRes(msg);
|
||||
if (msg.error) {
|
||||
toast(i18n.t(msg.error), 'danger');
|
||||
return;
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
this.state.enableNsfw = data.site.enable_nsfw;
|
||||
this.setState(this.state);
|
||||
document.title = `${i18n.t('create_community')} - ${data.site.name}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
73
ui/src/components/create-post.tsx
vendored
73
ui/src/components/create-post.tsx
vendored
|
@ -1,19 +1,59 @@
|
|||
import { Component } from 'inferno';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||
import { PostForm } from './post-form';
|
||||
import { toast, wsJsonToRes } from '../utils';
|
||||
import { WebSocketService } from '../services';
|
||||
import { PostFormParams } from '../interfaces';
|
||||
import {
|
||||
UserOperation,
|
||||
PostFormParams,
|
||||
WebSocketJsonResponse,
|
||||
GetSiteResponse,
|
||||
Site,
|
||||
} from '../interfaces';
|
||||
import { i18n } from '../i18next';
|
||||
|
||||
export class CreatePost extends Component<any, any> {
|
||||
interface CreatePostState {
|
||||
site: Site;
|
||||
}
|
||||
|
||||
export class CreatePost extends Component<any, CreatePostState> {
|
||||
private subscription: Subscription;
|
||||
private emptyState: CreatePostState = {
|
||||
site: {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
creator_id: undefined,
|
||||
published: undefined,
|
||||
creator_name: undefined,
|
||||
number_of_users: undefined,
|
||||
number_of_posts: undefined,
|
||||
number_of_comments: undefined,
|
||||
number_of_communities: undefined,
|
||||
enable_downvotes: undefined,
|
||||
open_registration: undefined,
|
||||
enable_nsfw: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
this.handlePostCreate = this.handlePostCreate.bind(this);
|
||||
this.state = this.emptyState;
|
||||
|
||||
this.subscription = WebSocketService.Instance.subject
|
||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||
.subscribe(
|
||||
msg => this.parseMessage(msg),
|
||||
err => console.error(err),
|
||||
() => console.log('complete')
|
||||
);
|
||||
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = `${i18n.t('create_post')} - ${
|
||||
WebSocketService.Instance.site.name
|
||||
}`;
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -22,7 +62,12 @@ export class CreatePost extends Component<any, any> {
|
|||
<div class="row">
|
||||
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
|
||||
<h5>{i18n.t('create_post')}</h5>
|
||||
<PostForm onCreate={this.handlePostCreate} params={this.params} />
|
||||
<PostForm
|
||||
onCreate={this.handlePostCreate}
|
||||
params={this.params}
|
||||
enableDownvotes={this.state.site.enable_downvotes}
|
||||
enableNsfw={this.state.site.enable_nsfw}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,4 +101,18 @@ export class CreatePost extends Component<any, any> {
|
|||
handlePostCreate(id: number) {
|
||||
this.props.history.push(`/post/${id}`);
|
||||
}
|
||||
|
||||
parseMessage(msg: WebSocketJsonResponse) {
|
||||
console.log(msg);
|
||||
let res = wsJsonToRes(msg);
|
||||
if (msg.error) {
|
||||
toast(i18n.t(msg.error), 'danger');
|
||||
return;
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
this.state.site = data.site;
|
||||
this.setState(this.state);
|
||||
document.title = `${i18n.t('create_post')} - ${data.site.name}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
42
ui/src/components/create-private-message.tsx
vendored
42
ui/src/components/create-private-message.tsx
vendored
|
@ -1,22 +1,38 @@
|
|||
import { Component } from 'inferno';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||
import { PrivateMessageForm } from './private-message-form';
|
||||
import { WebSocketService } from '../services';
|
||||
import { PrivateMessageFormParams } from '../interfaces';
|
||||
import { toast } from '../utils';
|
||||
import {
|
||||
UserOperation,
|
||||
WebSocketJsonResponse,
|
||||
GetSiteResponse,
|
||||
PrivateMessageFormParams,
|
||||
} from '../interfaces';
|
||||
import { toast, wsJsonToRes } from '../utils';
|
||||
import { i18n } from '../i18next';
|
||||
|
||||
export class CreatePrivateMessage extends Component<any, any> {
|
||||
private subscription: Subscription;
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
this.handlePrivateMessageCreate = this.handlePrivateMessageCreate.bind(
|
||||
this
|
||||
);
|
||||
|
||||
this.subscription = WebSocketService.Instance.subject
|
||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||
.subscribe(
|
||||
msg => this.parseMessage(msg),
|
||||
err => console.error(err),
|
||||
() => console.log('complete')
|
||||
);
|
||||
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = `${i18n.t('create_private_message')} - ${
|
||||
WebSocketService.Instance.site.name
|
||||
}`;
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -50,4 +66,18 @@ export class CreatePrivateMessage extends Component<any, any> {
|
|||
// Navigate to the front
|
||||
this.props.history.push(`/`);
|
||||
}
|
||||
|
||||
parseMessage(msg: WebSocketJsonResponse) {
|
||||
console.log(msg);
|
||||
let res = wsJsonToRes(msg);
|
||||
if (msg.error) {
|
||||
toast(i18n.t(msg.error), 'danger');
|
||||
return;
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
document.title = `${i18n.t('create_private_message')} - ${
|
||||
data.site.name
|
||||
}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
20
ui/src/components/inbox.tsx
vendored
20
ui/src/components/inbox.tsx
vendored
|
@ -16,6 +16,7 @@ import {
|
|||
GetPrivateMessagesForm,
|
||||
PrivateMessagesResponse,
|
||||
PrivateMessageResponse,
|
||||
GetSiteResponse,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
import {
|
||||
|
@ -56,6 +57,7 @@ interface InboxState {
|
|||
messages: Array<PrivateMessageI>;
|
||||
sort: SortType;
|
||||
page: number;
|
||||
enableDownvotes: boolean;
|
||||
}
|
||||
|
||||
export class Inbox extends Component<any, InboxState> {
|
||||
|
@ -68,6 +70,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
messages: [],
|
||||
sort: SortType.New,
|
||||
page: 1,
|
||||
enableDownvotes: undefined,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
|
@ -85,18 +88,13 @@ export class Inbox extends Component<any, InboxState> {
|
|||
);
|
||||
|
||||
this.refetch();
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = `/u/${UserService.Instance.user.username} ${i18n.t(
|
||||
'inbox'
|
||||
)} - ${WebSocketService.Instance.site.name}`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
|
@ -270,6 +268,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
noIndent
|
||||
markable
|
||||
showContext
|
||||
enableDownvotes={this.state.enableDownvotes}
|
||||
/>
|
||||
) : (
|
||||
<PrivateMessage privateMessage={i} />
|
||||
|
@ -287,6 +286,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
noIndent
|
||||
markable
|
||||
showContext
|
||||
enableDownvotes={this.state.enableDownvotes}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -301,6 +301,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
noIndent
|
||||
markable
|
||||
showContext
|
||||
enableDownvotes={this.state.enableDownvotes}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
@ -522,6 +523,13 @@ export class Inbox extends Component<any, InboxState> {
|
|||
let data = res.data as CommentResponse;
|
||||
createCommentLikeRes(data, this.state.replies);
|
||||
this.setState(this.state);
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
this.state.enableDownvotes = data.site.enable_downvotes;
|
||||
this.setState(this.state);
|
||||
document.title = `/u/${UserService.Instance.user.username} ${i18n.t(
|
||||
'inbox'
|
||||
)} - ${data.site.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
4
ui/src/components/login.tsx
vendored
4
ui/src/components/login.tsx
vendored
|
@ -385,9 +385,7 @@ export class Login extends Component<any, State> {
|
|||
let data = res.data as GetSiteResponse;
|
||||
this.state.enable_nsfw = data.site.enable_nsfw;
|
||||
this.setState(this.state);
|
||||
document.title = `${i18n.t('login')} - ${
|
||||
WebSocketService.Instance.site.name
|
||||
}`;
|
||||
document.title = `${i18n.t('login')} - ${data.site.name}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
5
ui/src/components/main.tsx
vendored
5
ui/src/components/main.tsx
vendored
|
@ -418,6 +418,8 @@ export class Main extends Component<any, MainState> {
|
|||
showCommunity
|
||||
removeDuplicates
|
||||
sort={this.state.sort}
|
||||
enableDownvotes={this.state.siteRes.site.enable_downvotes}
|
||||
enableNsfw={this.state.siteRes.site.enable_nsfw}
|
||||
/>
|
||||
) : (
|
||||
<CommentNodes
|
||||
|
@ -426,6 +428,7 @@ export class Main extends Component<any, MainState> {
|
|||
showCommunity
|
||||
sortType={this.state.sort}
|
||||
showContext
|
||||
enableDownvotes={this.state.siteRes.site.enable_downvotes}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -617,7 +620,7 @@ export class Main extends Component<any, MainState> {
|
|||
this.state.siteRes.banned = data.banned;
|
||||
this.state.siteRes.online = data.online;
|
||||
this.setState(this.state);
|
||||
document.title = `${WebSocketService.Instance.site.name}`;
|
||||
document.title = `${this.state.siteRes.site.name}`;
|
||||
} else if (res.op == UserOperation.EditSite) {
|
||||
let data = res.data as SiteResponse;
|
||||
this.state.siteRes.site = data.site;
|
||||
|
|
9
ui/src/components/modlog.tsx
vendored
9
ui/src/components/modlog.tsx
vendored
|
@ -16,6 +16,7 @@ import {
|
|||
ModAddCommunity,
|
||||
ModAdd,
|
||||
WebSocketJsonResponse,
|
||||
GetSiteResponse,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService } from '../services';
|
||||
import { wsJsonToRes, addTypeInfo, fetchLimit, toast } from '../utils';
|
||||
|
@ -64,16 +65,13 @@ export class Modlog extends Component<any, ModlogState> {
|
|||
);
|
||||
|
||||
this.refetch();
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = `Modlog - ${WebSocketService.Instance.site.name}`;
|
||||
}
|
||||
|
||||
setCombined(res: GetModlogResponse) {
|
||||
let removed_posts = addTypeInfo(res.removed_posts, 'removed_posts');
|
||||
let locked_posts = addTypeInfo(res.locked_posts, 'locked_posts');
|
||||
|
@ -434,6 +432,9 @@ export class Modlog extends Component<any, ModlogState> {
|
|||
this.state.loading = false;
|
||||
window.scrollTo(0, 0);
|
||||
this.setCombined(data);
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
document.title = `Modlog - ${data.site.name}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
3
ui/src/components/navbar.tsx
vendored
3
ui/src/components/navbar.tsx
vendored
|
@ -396,9 +396,6 @@ export class Navbar extends Component<any, NavbarState> {
|
|||
if (data.site && !this.state.siteName) {
|
||||
this.state.siteName = data.site.name;
|
||||
this.state.admins = data.admins;
|
||||
WebSocketService.Instance.site = data.site;
|
||||
WebSocketService.Instance.admins = data.admins;
|
||||
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
|
|
25
ui/src/components/password_change.tsx
vendored
25
ui/src/components/password_change.tsx
vendored
|
@ -6,6 +6,7 @@ import {
|
|||
LoginResponse,
|
||||
PasswordChangeForm,
|
||||
WebSocketJsonResponse,
|
||||
GetSiteResponse,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
import { wsJsonToRes, capitalizeFirstLetter, toast } from '../utils';
|
||||
|
@ -40,18 +41,13 @@ export class PasswordChange extends Component<any, State> {
|
|||
err => console.error(err),
|
||||
() => console.log('complete')
|
||||
);
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = `${i18n.t('password_change')} - ${
|
||||
WebSocketService.Instance.site.name
|
||||
}`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
|
@ -138,14 +134,15 @@ export class PasswordChange extends Component<any, State> {
|
|||
this.state.loading = false;
|
||||
this.setState(this.state);
|
||||
return;
|
||||
} else {
|
||||
if (res.op == UserOperation.PasswordChange) {
|
||||
let data = res.data as LoginResponse;
|
||||
this.state = this.emptyState;
|
||||
this.setState(this.state);
|
||||
UserService.Instance.login(data);
|
||||
this.props.history.push('/');
|
||||
}
|
||||
} else if (res.op == UserOperation.PasswordChange) {
|
||||
let data = res.data as LoginResponse;
|
||||
this.state = this.emptyState;
|
||||
this.setState(this.state);
|
||||
UserService.Instance.login(data);
|
||||
this.props.history.push('/');
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
document.title = `${i18n.t('password_change')} - ${data.site.name}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
25
ui/src/components/post-form.tsx
vendored
25
ui/src/components/post-form.tsx
vendored
|
@ -16,7 +16,6 @@ import {
|
|||
SearchForm,
|
||||
SearchType,
|
||||
SearchResponse,
|
||||
GetSiteResponse,
|
||||
WebSocketJsonResponse,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
|
@ -52,6 +51,8 @@ interface PostFormProps {
|
|||
onCancel?(): any;
|
||||
onCreate?(id: number): any;
|
||||
onEdit?(post: Post): any;
|
||||
enableNsfw: boolean;
|
||||
enableDownvotes: boolean;
|
||||
}
|
||||
|
||||
interface PostFormState {
|
||||
|
@ -63,7 +64,6 @@ interface PostFormState {
|
|||
suggestedTitle: string;
|
||||
suggestedPosts: Array<Post>;
|
||||
crossPosts: Array<Post>;
|
||||
enable_nsfw: boolean;
|
||||
}
|
||||
|
||||
export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||
|
@ -87,7 +87,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
suggestedTitle: undefined,
|
||||
suggestedPosts: [],
|
||||
crossPosts: [],
|
||||
enable_nsfw: undefined,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
|
@ -138,7 +137,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
};
|
||||
|
||||
WebSocketService.Instance.listCommunities(listCommunitiesForm);
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -240,7 +238,12 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
<div class="my-1 text-muted small font-weight-bold">
|
||||
{i18n.t('cross_posts')}
|
||||
</div>
|
||||
<PostListings showCommunity posts={this.state.crossPosts} />
|
||||
<PostListings
|
||||
showCommunity
|
||||
posts={this.state.crossPosts}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
enableNsfw={this.props.enableNsfw}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -265,7 +268,11 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
<div class="my-1 text-muted small font-weight-bold">
|
||||
{i18n.t('related_posts')}
|
||||
</div>
|
||||
<PostListings posts={this.state.suggestedPosts} />
|
||||
<PostListings
|
||||
posts={this.state.suggestedPosts}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
enableNsfw={this.props.enableNsfw}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -346,7 +353,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{this.state.enable_nsfw && (
|
||||
{this.props.enableNsfw && (
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-10">
|
||||
<div class="form-check">
|
||||
|
@ -631,10 +638,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
this.state.crossPosts = data.posts;
|
||||
}
|
||||
this.setState(this.state);
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
this.state.enable_nsfw = data.site.enable_nsfw;
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
6
ui/src/components/post-listing.tsx
vendored
6
ui/src/components/post-listing.tsx
vendored
|
@ -61,6 +61,8 @@ interface PostListingProps {
|
|||
showBody?: boolean;
|
||||
moderators?: Array<CommunityUser>;
|
||||
admins?: Array<UserView>;
|
||||
enableDownvotes: boolean;
|
||||
enableNsfw: boolean;
|
||||
}
|
||||
|
||||
export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||
|
@ -115,6 +117,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
post={this.props.post}
|
||||
onEdit={this.handleEditPost}
|
||||
onCancel={this.handleEditCancel}
|
||||
enableNsfw={this.props.enableNsfw}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -273,7 +277,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
>
|
||||
{this.state.score}
|
||||
</div>
|
||||
{WebSocketService.Instance.site.enable_downvotes && (
|
||||
{this.props.enableDownvotes && (
|
||||
<button
|
||||
className={`btn-animate btn btn-link p-0 ${
|
||||
this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
|
||||
|
|
4
ui/src/components/post-listings.tsx
vendored
4
ui/src/components/post-listings.tsx
vendored
|
@ -11,6 +11,8 @@ interface PostListingsProps {
|
|||
showCommunity?: boolean;
|
||||
removeDuplicates?: boolean;
|
||||
sort?: SortType;
|
||||
enableDownvotes: boolean;
|
||||
enableNsfw: boolean;
|
||||
}
|
||||
|
||||
export class PostListings extends Component<PostListingsProps, any> {
|
||||
|
@ -27,6 +29,8 @@ export class PostListings extends Component<PostListingsProps, any> {
|
|||
<PostListing
|
||||
post={post}
|
||||
showCommunity={this.props.showCommunity}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
enableNsfw={this.props.enableNsfw}
|
||||
/>
|
||||
<hr class="my-2" />
|
||||
</>
|
||||
|
|
52
ui/src/components/post.tsx
vendored
52
ui/src/components/post.tsx
vendored
|
@ -18,7 +18,6 @@ import {
|
|||
BanUserResponse,
|
||||
AddModToCommunityResponse,
|
||||
AddAdminResponse,
|
||||
UserView,
|
||||
SearchType,
|
||||
SortType,
|
||||
SearchForm,
|
||||
|
@ -52,12 +51,12 @@ interface PostState {
|
|||
commentSort: CommentSortType;
|
||||
community: Community;
|
||||
moderators: Array<CommunityUser>;
|
||||
admins: Array<UserView>;
|
||||
online: number;
|
||||
scrolled?: boolean;
|
||||
scrolled_comment_id?: number;
|
||||
loading: boolean;
|
||||
crossPosts: Array<PostI>;
|
||||
siteRes: GetSiteResponse;
|
||||
}
|
||||
|
||||
export class Post extends Component<any, PostState> {
|
||||
|
@ -68,11 +67,29 @@ export class Post extends Component<any, PostState> {
|
|||
commentSort: CommentSortType.Hot,
|
||||
community: null,
|
||||
moderators: [],
|
||||
admins: [],
|
||||
online: null,
|
||||
scrolled: false,
|
||||
loading: true,
|
||||
crossPosts: [],
|
||||
siteRes: {
|
||||
admins: [],
|
||||
banned: [],
|
||||
site: {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
creator_id: undefined,
|
||||
published: undefined,
|
||||
creator_name: undefined,
|
||||
number_of_users: undefined,
|
||||
number_of_posts: undefined,
|
||||
number_of_comments: undefined,
|
||||
number_of_communities: undefined,
|
||||
enable_downvotes: undefined,
|
||||
open_registration: undefined,
|
||||
enable_nsfw: undefined,
|
||||
},
|
||||
online: null,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
|
@ -97,6 +114,7 @@ export class Post extends Component<any, PostState> {
|
|||
id: postId,
|
||||
};
|
||||
WebSocketService.Instance.getPost(form);
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -180,7 +198,9 @@ export class Post extends Component<any, PostState> {
|
|||
showBody
|
||||
showCommunity
|
||||
moderators={this.state.moderators}
|
||||
admins={this.state.admins}
|
||||
admins={this.state.siteRes.admins}
|
||||
enableDownvotes={this.state.siteRes.site.enable_downvotes}
|
||||
enableNsfw={this.state.siteRes.site.enable_nsfw}
|
||||
/>
|
||||
<div className="mb-2" />
|
||||
<CommentForm
|
||||
|
@ -269,9 +289,10 @@ export class Post extends Component<any, PostState> {
|
|||
noIndent
|
||||
locked={this.state.post.locked}
|
||||
moderators={this.state.moderators}
|
||||
admins={this.state.admins}
|
||||
admins={this.state.siteRes.admins}
|
||||
postCreatorId={this.state.post.creator_id}
|
||||
showContext
|
||||
enableDownvotes={this.state.siteRes.site.enable_downvotes}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -284,8 +305,9 @@ export class Post extends Component<any, PostState> {
|
|||
<Sidebar
|
||||
community={this.state.community}
|
||||
moderators={this.state.moderators}
|
||||
admins={this.state.admins}
|
||||
admins={this.state.siteRes.admins}
|
||||
online={this.state.online}
|
||||
enableNsfw={this.state.siteRes.site.enable_nsfw}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -336,9 +358,10 @@ export class Post extends Component<any, PostState> {
|
|||
nodes={nodes}
|
||||
locked={this.state.post.locked}
|
||||
moderators={this.state.moderators}
|
||||
admins={this.state.admins}
|
||||
admins={this.state.siteRes.admins}
|
||||
postCreatorId={this.state.post.creator_id}
|
||||
sort={this.state.commentSort}
|
||||
enableDownvotes={this.state.siteRes.site.enable_downvotes}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -360,10 +383,10 @@ export class Post extends Component<any, PostState> {
|
|||
this.state.comments = data.comments;
|
||||
this.state.community = data.community;
|
||||
this.state.moderators = data.moderators;
|
||||
this.state.admins = data.admins;
|
||||
this.state.siteRes.admins = data.admins;
|
||||
this.state.online = data.online;
|
||||
this.state.loading = false;
|
||||
document.title = `${this.state.post.name} - ${WebSocketService.Instance.site.name}`;
|
||||
document.title = `${this.state.post.name} - ${this.state.siteRes.site.name}`;
|
||||
|
||||
// Get cross-posts
|
||||
if (this.state.post.url) {
|
||||
|
@ -450,7 +473,7 @@ export class Post extends Component<any, PostState> {
|
|||
this.setState(this.state);
|
||||
} else if (res.op == UserOperation.AddAdmin) {
|
||||
let data = res.data as AddAdminResponse;
|
||||
this.state.admins = data.admins;
|
||||
this.state.siteRes.admins = data.admins;
|
||||
this.setState(this.state);
|
||||
} else if (res.op == UserOperation.Search) {
|
||||
let data = res.data as SearchResponse;
|
||||
|
@ -461,15 +484,18 @@ export class Post extends Component<any, PostState> {
|
|||
this.state.post.duplicates = this.state.crossPosts;
|
||||
}
|
||||
this.setState(this.state);
|
||||
} else if (res.op == UserOperation.TransferSite) {
|
||||
} else if (
|
||||
res.op == UserOperation.TransferSite ||
|
||||
res.op == UserOperation.GetSite
|
||||
) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
this.state.admins = data.admins;
|
||||
this.state.siteRes = data;
|
||||
this.setState(this.state);
|
||||
} else if (res.op == UserOperation.TransferCommunity) {
|
||||
let data = res.data as GetCommunityResponse;
|
||||
this.state.community = data.community;
|
||||
this.state.moderators = data.moderators;
|
||||
this.state.admins = data.admins;
|
||||
this.state.siteRes.admins = data.admins;
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
|
|
1
ui/src/components/private-message-form.tsx
vendored
1
ui/src/components/private-message-form.tsx
vendored
|
@ -1,6 +1,5 @@
|
|||
import { Component, linkEvent } from 'inferno';
|
||||
import { Prompt } from 'inferno-router';
|
||||
import { Link } from 'inferno-router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||
import {
|
||||
|
|
48
ui/src/components/search.tsx
vendored
48
ui/src/components/search.tsx
vendored
|
@ -15,6 +15,8 @@ import {
|
|||
PostResponse,
|
||||
CommentResponse,
|
||||
WebSocketJsonResponse,
|
||||
GetSiteResponse,
|
||||
Site,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService } from '../services';
|
||||
import {
|
||||
|
@ -41,6 +43,7 @@ interface SearchState {
|
|||
page: number;
|
||||
searchResponse: SearchResponse;
|
||||
loading: boolean;
|
||||
site: Site;
|
||||
}
|
||||
|
||||
export class Search extends Component<any, SearchState> {
|
||||
|
@ -58,6 +61,20 @@ export class Search extends Component<any, SearchState> {
|
|||
users: [],
|
||||
},
|
||||
loading: false,
|
||||
site: {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
creator_id: undefined,
|
||||
published: undefined,
|
||||
creator_name: undefined,
|
||||
number_of_users: undefined,
|
||||
number_of_posts: undefined,
|
||||
number_of_comments: undefined,
|
||||
number_of_communities: undefined,
|
||||
enable_downvotes: undefined,
|
||||
open_registration: undefined,
|
||||
enable_nsfw: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
getSearchQueryFromProps(props: any): string {
|
||||
|
@ -94,6 +111,8 @@ export class Search extends Component<any, SearchState> {
|
|||
() => console.log('complete')
|
||||
);
|
||||
|
||||
WebSocketService.Instance.getSite();
|
||||
|
||||
if (this.state.q) {
|
||||
this.search();
|
||||
}
|
||||
|
@ -118,12 +137,6 @@ export class Search extends Component<any, SearchState> {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = `${i18n.t('search')} - ${
|
||||
WebSocketService.Instance.site.name
|
||||
}`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
|
@ -241,13 +254,19 @@ export class Search extends Component<any, SearchState> {
|
|||
<div class="row">
|
||||
<div class="col-12">
|
||||
{i.type_ == 'posts' && (
|
||||
<PostListing post={i.data as Post} showCommunity />
|
||||
<PostListing
|
||||
post={i.data as Post}
|
||||
showCommunity
|
||||
enableDownvotes={this.state.site.enable_downvotes}
|
||||
enableNsfw={this.state.site.enable_nsfw}
|
||||
/>
|
||||
)}
|
||||
{i.type_ == 'comments' && (
|
||||
<CommentNodes
|
||||
nodes={[{ comment: i.data as Comment }]}
|
||||
locked
|
||||
noIndent
|
||||
enableDownvotes={this.state.site.enable_downvotes}
|
||||
/>
|
||||
)}
|
||||
{i.type_ == 'communities' && (
|
||||
|
@ -281,6 +300,7 @@ export class Search extends Component<any, SearchState> {
|
|||
nodes={commentsToFlatNodes(this.state.searchResponse.comments)}
|
||||
locked
|
||||
noIndent
|
||||
enableDownvotes={this.state.site.enable_downvotes}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -291,7 +311,12 @@ export class Search extends Component<any, SearchState> {
|
|||
{this.state.searchResponse.posts.map(post => (
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<PostListing post={post} showCommunity />
|
||||
<PostListing
|
||||
post={post}
|
||||
showCommunity
|
||||
enableDownvotes={this.state.site.enable_downvotes}
|
||||
enableNsfw={this.state.site.enable_nsfw}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
@ -455,7 +480,7 @@ export class Search extends Component<any, SearchState> {
|
|||
this.state.searchResponse = data;
|
||||
this.state.loading = false;
|
||||
document.title = `${i18n.t('search')} - ${this.state.q} - ${
|
||||
WebSocketService.Instance.site.name
|
||||
this.state.site.name
|
||||
}`;
|
||||
window.scrollTo(0, 0);
|
||||
this.setState(this.state);
|
||||
|
@ -467,6 +492,11 @@ export class Search extends Component<any, SearchState> {
|
|||
let data = res.data as PostResponse;
|
||||
createPostLikeFindRes(data, this.state.searchResponse.posts);
|
||||
this.setState(this.state);
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
this.state.site = data.site;
|
||||
this.setState(this.state);
|
||||
document.title = `${i18n.t('search')} - ${data.site.name}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
2
ui/src/components/sidebar.tsx
vendored
2
ui/src/components/sidebar.tsx
vendored
|
@ -19,6 +19,7 @@ interface SidebarProps {
|
|||
moderators: Array<CommunityUser>;
|
||||
admins: Array<UserView>;
|
||||
online: number;
|
||||
enableNsfw: boolean;
|
||||
}
|
||||
|
||||
interface SidebarState {
|
||||
|
@ -53,6 +54,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
community={this.props.community}
|
||||
onEdit={this.handleEditCommunity}
|
||||
onCancel={this.handleEditCancel}
|
||||
enableNsfw={this.props.enableNsfw}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
38
ui/src/components/sponsors.tsx
vendored
38
ui/src/components/sponsors.tsx
vendored
|
@ -1,8 +1,15 @@
|
|||
import { Component } from 'inferno';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||
import { WebSocketService } from '../services';
|
||||
import {
|
||||
GetSiteResponse,
|
||||
WebSocketJsonResponse,
|
||||
UserOperation,
|
||||
} from '../interfaces';
|
||||
import { i18n } from '../i18next';
|
||||
import { T } from 'inferno-i18next';
|
||||
import { repoUrl } from '../utils';
|
||||
import { repoUrl, wsJsonToRes, toast } from '../utils';
|
||||
|
||||
interface SilverUser {
|
||||
name: string;
|
||||
|
@ -33,17 +40,28 @@ let silver: Array<SilverUser> = [
|
|||
// let latinum = [];
|
||||
|
||||
export class Sponsors extends Component<any, any> {
|
||||
private subscription: Subscription;
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
this.subscription = WebSocketService.Instance.subject
|
||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||
.subscribe(
|
||||
msg => this.parseMessage(msg),
|
||||
err => console.error(err),
|
||||
() => console.log('complete')
|
||||
);
|
||||
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = `${i18n.t('sponsors')} - ${
|
||||
WebSocketService.Instance.site.name
|
||||
}`;
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container text-center">
|
||||
|
@ -153,4 +171,16 @@ export class Sponsors extends Component<any, any> {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
parseMessage(msg: WebSocketJsonResponse) {
|
||||
console.log(msg);
|
||||
let res = wsJsonToRes(msg);
|
||||
if (msg.error) {
|
||||
toast(i18n.t(msg.error), 'danger');
|
||||
return;
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
document.title = `${i18n.t('sponsors')} - ${data.site.name}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
38
ui/src/components/user.tsx
vendored
38
ui/src/components/user.tsx
vendored
|
@ -20,6 +20,8 @@ import {
|
|||
DeleteAccountForm,
|
||||
PostResponse,
|
||||
WebSocketJsonResponse,
|
||||
GetSiteResponse,
|
||||
Site,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
import {
|
||||
|
@ -74,6 +76,7 @@ interface UserState {
|
|||
deleteAccountLoading: boolean;
|
||||
deleteAccountShowConfirm: boolean;
|
||||
deleteAccountForm: DeleteAccountForm;
|
||||
site: Site;
|
||||
}
|
||||
|
||||
export class User extends Component<any, UserState> {
|
||||
|
@ -122,6 +125,20 @@ export class User extends Component<any, UserState> {
|
|||
deleteAccountForm: {
|
||||
password: null,
|
||||
},
|
||||
site: {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
creator_id: undefined,
|
||||
published: undefined,
|
||||
creator_name: undefined,
|
||||
number_of_users: undefined,
|
||||
number_of_posts: undefined,
|
||||
number_of_comments: undefined,
|
||||
number_of_communities: undefined,
|
||||
enable_downvotes: undefined,
|
||||
open_registration: undefined,
|
||||
enable_nsfw: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
|
@ -148,6 +165,7 @@ export class User extends Component<any, UserState> {
|
|||
);
|
||||
|
||||
this.refetch();
|
||||
WebSocketService.Instance.getSite();
|
||||
}
|
||||
|
||||
get isCurrentUser() {
|
||||
|
@ -356,6 +374,8 @@ export class User extends Component<any, UserState> {
|
|||
post={i.data as Post}
|
||||
admins={this.state.admins}
|
||||
showCommunity
|
||||
enableDownvotes={this.state.site.enable_downvotes}
|
||||
enableNsfw={this.state.site.enable_nsfw}
|
||||
/>
|
||||
) : (
|
||||
<CommentNodes
|
||||
|
@ -363,6 +383,7 @@ export class User extends Component<any, UserState> {
|
|||
admins={this.state.admins}
|
||||
noIndent
|
||||
showContext
|
||||
enableDownvotes={this.state.site.enable_downvotes}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -379,6 +400,7 @@ export class User extends Component<any, UserState> {
|
|||
admins={this.state.admins}
|
||||
noIndent
|
||||
showContext
|
||||
enableDownvotes={this.state.site.enable_downvotes}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -388,7 +410,13 @@ export class User extends Component<any, UserState> {
|
|||
return (
|
||||
<div>
|
||||
{this.state.posts.map(post => (
|
||||
<PostListing post={post} admins={this.state.admins} showCommunity />
|
||||
<PostListing
|
||||
post={post}
|
||||
admins={this.state.admins}
|
||||
showCommunity
|
||||
enableDownvotes={this.state.site.enable_downvotes}
|
||||
enableNsfw={this.state.site.enable_nsfw}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
@ -670,7 +698,7 @@ export class User extends Component<any, UserState> {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
{WebSocketService.Instance.site.enable_nsfw && (
|
||||
{this.state.site.enable_nsfw && (
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
|
@ -1107,7 +1135,7 @@ export class User extends Component<any, UserState> {
|
|||
UserService.Instance.user.show_avatars;
|
||||
this.state.userSettingsForm.matrix_user_id = this.state.user.matrix_user_id;
|
||||
}
|
||||
document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
|
||||
document.title = `/u/${this.state.user.name} - ${this.state.site.name}`;
|
||||
window.scrollTo(0, 0);
|
||||
this.setState(this.state);
|
||||
setupTippy();
|
||||
|
@ -1159,6 +1187,10 @@ export class User extends Component<any, UserState> {
|
|||
this.state.deleteAccountShowConfirm = false;
|
||||
this.setState(this.state);
|
||||
this.context.router.history.push('/');
|
||||
} else if (res.op == UserOperation.GetSite) {
|
||||
let data = res.data as GetSiteResponse;
|
||||
this.state.site = data.site;
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
2
ui/src/services/WebSocketService.ts
vendored
2
ui/src/services/WebSocketService.ts
vendored
|
@ -25,7 +25,6 @@ import {
|
|||
TransferSiteForm,
|
||||
BanUserForm,
|
||||
SiteForm,
|
||||
Site,
|
||||
UserView,
|
||||
GetRepliesForm,
|
||||
GetUserMentionsForm,
|
||||
|
@ -57,7 +56,6 @@ export class WebSocketService {
|
|||
public ws: ReconnectingWebSocket;
|
||||
public subject: Observable<any>;
|
||||
|
||||
public site: Site;
|
||||
public admins: Array<UserView>;
|
||||
public banned: Array<UserView>;
|
||||
|
||||
|
|
2
ui/src/utils.ts
vendored
2
ui/src/utils.ts
vendored
|
@ -923,7 +923,7 @@ export function postSort(
|
|||
+a.removed - +b.removed ||
|
||||
+a.deleted - +b.deleted ||
|
||||
(communityType && +b.stickied - +a.stickied) ||
|
||||
hotRankPost(b) - hotRankPost(a)
|
||||
b.hot_rank - a.hot_rank
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
2
ui/src/version.ts
vendored
2
ui/src/version.ts
vendored
|
@ -1 +1 @@
|
|||
export const version: string = 'v0.7.11';
|
||||
export const version: string = 'v0.7.13';
|
||||
|
|
337
ui/translations/ca.json
vendored
337
ui/translations/ca.json
vendored
|
@ -1,99 +1,105 @@
|
|||
{
|
||||
"post": "Publicar",
|
||||
"remove_post": "Eliminar publicació",
|
||||
"no_posts": "Sense publicacions.",
|
||||
"remove_post": "Suprimir publicació",
|
||||
"no_posts": "No hi han publicacions.",
|
||||
"create_a_post": "Crear una publicació",
|
||||
"create_post": "Crear Publicació",
|
||||
"number_of_posts": "{{count}} Publicacions",
|
||||
"create_post": "Crear publicació",
|
||||
"number_of_posts": "{{count}} Publicació",
|
||||
"number_of_posts_plural": "{{count}} Publicacions",
|
||||
"posts": "Publicacions",
|
||||
"related_posts": "Aquestes publicacions podrien estar relacionades",
|
||||
"cross_posts": "Aquest link també ha sigut publicat en:",
|
||||
"cross_posts": "Aquest enllaç també s’ha publicat en:",
|
||||
"cross_post": "cross-post",
|
||||
"comments": "Comentaris",
|
||||
"number_of_comments": "{{count}} Comentaris",
|
||||
"remove_comment": "Eliminar Comentaris",
|
||||
"number_of_comments": "{{count}} comentari",
|
||||
"number_of_comments_plural": "{{count}} comentaris",
|
||||
"remove_comment": "Suprimeix el comentari",
|
||||
"communities": "Comunitats",
|
||||
"users": "Usuaris",
|
||||
"create_a_community": "Crear una comunitat",
|
||||
"create_community": "Crear Comunitat",
|
||||
"remove_community": "Eliminar Comunitat",
|
||||
"create_a_community": "Crea una comunitat",
|
||||
"create_community": "Crea una comunitat",
|
||||
"remove_community": "Suprimeix la comunitat",
|
||||
"subscribed_to_communities": "Subscrit a <1>comunitats</1>",
|
||||
"trending_communities": "<1>Comunitats</1> en tendència",
|
||||
"list_of_communities": "Llista de comunitats",
|
||||
"number_of_communities": "{{count}} Comunitats",
|
||||
"number_of_communities": "{{count}} comunitat",
|
||||
"number_of_communities_plural": "{{count}} comunitats",
|
||||
"community_reqs": "minúscules, guió baix, i sense espais.",
|
||||
"create_private_message": "Crear Missatge Privat",
|
||||
"send_secure_message": "Enviar Missatge Segur",
|
||||
"send_message": "Enviar Missatge",
|
||||
"create_private_message": "Crea un missatge privat",
|
||||
"send_secure_message": "Envia un missatge segur",
|
||||
"send_message": "Envia el missatge",
|
||||
"message": "Missatge",
|
||||
"edit": "editar",
|
||||
"reply": "respondre",
|
||||
"cancel": "Cancelar",
|
||||
"preview": "Previsualitzar",
|
||||
"upload_image": "pujar imatge",
|
||||
"edit": "edita",
|
||||
"reply": "respon",
|
||||
"cancel": "Cancel·la",
|
||||
"preview": "Previsualitza",
|
||||
"upload_image": "puja una imatge",
|
||||
"avatar": "Avatar",
|
||||
"upload_avatar": "Pujar Avatar",
|
||||
"show_avatars": "Veure Avatares",
|
||||
"formatting_help": "Ajuda de format",
|
||||
"view_source": "veure font",
|
||||
"unlock": "desbloquejar",
|
||||
"lock": "bloquejar",
|
||||
"upload_avatar": "Puja un avatar",
|
||||
"show_avatars": "Mostra els avatars",
|
||||
"formatting_help": "ajuda amb la formatació",
|
||||
"view_source": "mostra’n el codi font",
|
||||
"unlock": "desbloca",
|
||||
"lock": "bloca",
|
||||
"sticky": "fijat",
|
||||
"unsticky": "no fijat",
|
||||
"link": "link",
|
||||
"archive_link": "arxivar link",
|
||||
"link": "enllaç",
|
||||
"archive_link": "enllaç de l’arxiu",
|
||||
"mod": "moderador",
|
||||
"mods": "moderadores",
|
||||
"moderates": "Modera",
|
||||
"settings": "Configuració",
|
||||
"remove_as_mod": "eliminar com moderador",
|
||||
"appoint_as_mod": "designar com moderador",
|
||||
"remove_as_mod": "suprimeix com a moderador",
|
||||
"appoint_as_mod": "designa com a moderador",
|
||||
"modlog": "Historial de moderació",
|
||||
"admin": "administrador",
|
||||
"admins": "administradors",
|
||||
"remove_as_admin": "eliminar com administrador",
|
||||
"appoint_as_admin": "designar com administrador",
|
||||
"remove": "eliminar",
|
||||
"removed": "eliminat",
|
||||
"locked": "bloquejat",
|
||||
"remove_as_admin": "suprimeix com a administrador",
|
||||
"appoint_as_admin": "designa com a administrador",
|
||||
"remove": "suprimeix",
|
||||
"removed": "suprimit pel moderador",
|
||||
"locked": "blocat",
|
||||
"stickied": "fijat",
|
||||
"reason": "Raó",
|
||||
"mark_as_read": "marcar com llegit",
|
||||
"mark_as_unread": "marcar com no llegit",
|
||||
"delete": "eliminar",
|
||||
"deleted": "eliminat",
|
||||
"delete_account": "Eliminar Compte",
|
||||
"delete_account_confirm":
|
||||
"Avís: aquesta acció eliminarà permanentment la teva informació. Introdueix la teva contrasenya per a continuar",
|
||||
"restore": "restaurar",
|
||||
"ban": "expulsar",
|
||||
"ban_from_site": "expulsar del lloc",
|
||||
"unban": "admetre",
|
||||
"unban_from_site": "admetre al lloc",
|
||||
"mark_as_read": "marca com a llegit",
|
||||
"mark_as_unread": "marca com a no llegit",
|
||||
"delete": "suprimeix",
|
||||
"deleted": "suprimit pel creador",
|
||||
"delete_account": "Suprimeix el compte",
|
||||
"delete_account_confirm": "Atenció: aquesta acció suprimirà permanentment la vostra informació. Introduïu la vostra contrasenya per a confirmar.",
|
||||
"restore": "restaura",
|
||||
"ban": "expulsa",
|
||||
"ban_from_site": "expulsa del lloc",
|
||||
"unban": "readmet",
|
||||
"unban_from_site": "readmet al lloc",
|
||||
"banned": "expulsat",
|
||||
"save": "guardar",
|
||||
"unsave": "descartar",
|
||||
"create": "crear",
|
||||
"save": "desa",
|
||||
"unsave": "descarta",
|
||||
"create": "crea",
|
||||
"creator": "creador",
|
||||
"username": "Nom d'Usuari",
|
||||
"email_or_username": "Correu o Usuari",
|
||||
"number_of_users": "{{count}} Usuaris",
|
||||
"number_of_subscribers": "{{count}} Subscriptors",
|
||||
"number_of_points": "{{count}} Punts",
|
||||
"number_online": "{{count}} Usauris En Línia",
|
||||
"username": "Nom d’usuari",
|
||||
"email_or_username": "Adreça electrònica o usuari",
|
||||
"number_of_users": "{{count}} usuari",
|
||||
"number_of_users_plural": "{{count}} usuaris",
|
||||
"number_of_subscribers": "{{count}} subscriptor",
|
||||
"number_of_subscribers_plural": "{{count}} subscriptors",
|
||||
"number_of_points": "{{count}} punt",
|
||||
"number_of_points_plural": "{{count}} punts",
|
||||
"number_online": "{{count}} usuari en línia",
|
||||
"number_online_plural": "{{count}} usuaris en línia",
|
||||
"name": "Nom",
|
||||
"title": "Titol",
|
||||
"title": "Títol",
|
||||
"category": "Categoria",
|
||||
"subscribers": "Suscriptors",
|
||||
"both": "Ambdos",
|
||||
"saved": "Guardat",
|
||||
"unsubscribe": "Desubscriure's",
|
||||
"subscribe": "Subscriure's",
|
||||
"subscribers": "Subscriptors",
|
||||
"both": "Tots dos",
|
||||
"saved": "Desat",
|
||||
"unsubscribe": "Dóna’t de baixa",
|
||||
"subscribe": "Subscriu-t’hi",
|
||||
"subscribed": "Subscrit",
|
||||
"prev": "Anterior",
|
||||
"next": "Següent",
|
||||
"sidebar": "Descripció de la comunitat",
|
||||
"sort_type": "Tipus d'orden",
|
||||
"sidebar": "Barra lateral",
|
||||
"sort_type": "Tipus d’ordenació",
|
||||
"hot": "Popular",
|
||||
"new": "Nou",
|
||||
"top_day": "El millor del dia",
|
||||
|
@ -103,75 +109,71 @@
|
|||
"all": "Tot",
|
||||
"top": "Millor",
|
||||
"api": "API",
|
||||
"docs": "Docs",
|
||||
"inbox": "Bústia d'entrada",
|
||||
"inbox_for": "Bústia d'entrada per a <1>{{user}}</1>",
|
||||
"mark_all_as_read": "marcar tot com llegit",
|
||||
"docs": "Documentació",
|
||||
"inbox": "Bústia d’entrada",
|
||||
"inbox_for": "Bústia d’entrada per a <1>{{user}}</1>",
|
||||
"mark_all_as_read": "marca-ho tot com a llegit",
|
||||
"type": "Tipus",
|
||||
"unread": "No llegit",
|
||||
"replies": "Respostes",
|
||||
"mentions": "Menciones",
|
||||
"reply_sent": "Resposta enviada",
|
||||
"message_sent": "Missatge enviado",
|
||||
"search": "Buscar",
|
||||
"overview": "Resum",
|
||||
"mentions": "Mencions",
|
||||
"reply_sent": "La resposta ha estat enviada",
|
||||
"message_sent": "El missatge ha estat enviat",
|
||||
"search": "Cerca",
|
||||
"overview": "Visió de conjunt",
|
||||
"view": "Vista",
|
||||
"logout": "Tancar sessió",
|
||||
"login_sign_up": "Iniciar sessió / Crear compte",
|
||||
"login": "Iniciar sessió",
|
||||
"sign_up": "Crear compte",
|
||||
"notifications_error":
|
||||
"Notificacions d'escriptori no disponibles al teu navegador. Prova amb Firefox o Chrome.",
|
||||
"logout": "Finalitza la sessió",
|
||||
"login_sign_up": "Inicia una sessió / crea un compte",
|
||||
"login": "Inicia una sessió",
|
||||
"sign_up": "Crea un compte",
|
||||
"notifications_error": "Les notificacions d’escriptori no estan disponibles al vostre navegador. Proveu amb el Firefox o el Chrome.",
|
||||
"unread_messages": "Missatges no llegits",
|
||||
"messages": "Missatges",
|
||||
"password": "Contrasenya",
|
||||
"verify_password": "Verificar Contrasenya",
|
||||
"old_password": "Antiga Contrasenya",
|
||||
"forgot_password": "oblidí la meva contrasenya",
|
||||
"reset_password_mail_sent": "Enviar correu per a restablir la contrasenya.",
|
||||
"password_change": "Canvi de Contrasenya",
|
||||
"new_password": "Nueva Contrasenya",
|
||||
"no_email_setup": "Aquest servidor no ha activat correctament el correu.",
|
||||
"verify_password": "Verifica la contrasenya",
|
||||
"old_password": "Contrasenya antiga",
|
||||
"forgot_password": "vaig oblidar la meva contrasenya",
|
||||
"reset_password_mail_sent": "Hem enviat un missatge per correu per a restablir la contrasenya.",
|
||||
"password_change": "Canvi de contrasenya",
|
||||
"new_password": "Contrasenya nova",
|
||||
"no_email_setup": "Aquest servidor no ha configurat correctament el correu.",
|
||||
"email": "Correu electrònic",
|
||||
"matrix_user_id": "Usuari Matricial",
|
||||
"private_message_disclaimer":
|
||||
"Avís: Els missatges privats en Lemmy no són segurs. Sisplau creu un compte en <1>Riot.im</1> per a mensajeria segura.",
|
||||
"send_notifications_to_email": "Enviar notificacions al correu",
|
||||
"matrix_user_id": "Usuari del Matrix",
|
||||
"private_message_disclaimer": "Atenció: els missatges privats al Lemmy no són segurs. Creeu-vos un compte a <1>Riot.im</1> per a una missatgeria segura.",
|
||||
"send_notifications_to_email": "Envia notificacions al correu",
|
||||
"optional": "Opcional",
|
||||
"expires": "Expira",
|
||||
"language": "Llenguatge",
|
||||
"language": "Llengua",
|
||||
"browser_default": "Per defecte del navegador",
|
||||
"downvotes_disabled": "Vots negatius deshabilitats",
|
||||
"enable_downvotes": "Habilitar vots negatius",
|
||||
"open_registration": "Obrir registre",
|
||||
"registration_closed": "Registre tancat",
|
||||
"enable_nsfw": "Habilitar NSFW",
|
||||
"downvotes_disabled": "Vots negatius inhabilitats",
|
||||
"enable_downvotes": "Habilita els vots negatius",
|
||||
"open_registration": "Obre els registres",
|
||||
"registration_closed": "S’han tancat els registres",
|
||||
"enable_nsfw": "Habilita el contingut per a adults",
|
||||
"url": "URL",
|
||||
"body": "Descripció",
|
||||
"copy_suggested_title": "Copiar el títol sugerido: {{title}}",
|
||||
"body": "Corps",
|
||||
"copy_suggested_title": "copia el títol suggerit: {{title}}",
|
||||
"community": "Comunitat",
|
||||
"expand_here": "Expandir ací",
|
||||
"subscribe_to_communities": "Subscriure's a algunes <1>comunitats</1>.",
|
||||
"chat": "Chat",
|
||||
"expand_here": "Expandeix ací",
|
||||
"subscribe_to_communities": "Subscriviu-vos a algunes <1>comunitats</1>.",
|
||||
"chat": "Xat",
|
||||
"recent_comments": "Comentaris recients",
|
||||
"no_results": "Sense resultats.",
|
||||
"setup": "Configurar",
|
||||
"lemmy_instance_setup": "Configuració d'instancia de Lemmy",
|
||||
"setup_admin": "Configurar administrador del Lloc",
|
||||
"your_site": "el teu lloc",
|
||||
"lemmy_instance_setup": "Configuració d’instància del Lemmy",
|
||||
"setup_admin": "Configura administrador del lloc",
|
||||
"your_site": "el vostre lloc",
|
||||
"modified": "modificat",
|
||||
"nsfw": "NSFW",
|
||||
"show_nsfw": "Mostrar contingut NSFW",
|
||||
"nsfw": "Per a adults",
|
||||
"show_nsfw": "Mostra el contingut per a adults",
|
||||
"theme": "Tema",
|
||||
"sponsors": "Patrocinadors",
|
||||
"sponsors_of_lemmy": "Patrocinadors de Lemmy",
|
||||
"sponsor_message":
|
||||
"Lemmy és programari lliure i de <1>codi obert</1>, la qual cosa significa que no tindrà publicitats, monetització, ni capitals emprenedors, mai. Les teves donacions secunden directament el desenvolupament a temps complet del projecte. Moltes gràcies a les següents persones:",
|
||||
"support_on_patreon": "Suport a Patreon",
|
||||
"sponsors_of_lemmy": "Patrocinadors del Lemmy",
|
||||
"sponsor_message": "El Lemmy és programari lliure i de <1>codi obert</1>, la qual cosa significa que no tindrà publicitats, monetització, ni capitals emprenedors, mai. Les vostres donacions donen suport directament al desenvolupament a temps complet del projecte. Moltes gràcies a les següents persones:",
|
||||
"support_on_patreon": "Suport al Patreon",
|
||||
"donate_to_lemmy": "Donar a Lemmy",
|
||||
"donate": "Donar",
|
||||
"general_sponsors":
|
||||
"Los Patrocinadores Generales son aquellos que señaron entre $10 y $39 a Lemmy.",
|
||||
"donate": "Dona",
|
||||
"general_sponsors": "Els patrocinadors generals són aquells que es van comprometre a donar entre 10 y 39 $ al Lemmy.",
|
||||
"crypto": "Crypto",
|
||||
"bitcoin": "Bitcoin",
|
||||
"ethereum": "Ethereum",
|
||||
|
@ -181,57 +183,78 @@
|
|||
"by": "per",
|
||||
"to": "a",
|
||||
"from": "des de",
|
||||
"transfer_community": "transferir comunitat",
|
||||
"transfer_site": "transferir lloc",
|
||||
"are_you_sure": "Ets segur?",
|
||||
"transfer_community": "transfereix la comunitat",
|
||||
"transfer_site": "transfereix el lloc",
|
||||
"are_you_sure": "n’esteu segur?",
|
||||
"yes": "sí",
|
||||
"no": "no",
|
||||
"powered_by": "Impulsat per",
|
||||
"landing_0":
|
||||
"Lemmy és un <1>agregador de links</1> / alternativa a reddit, amb la intenció de funcionar al <2>fedivers</2>.<3></3>És allotjable per un mateix (sense necessitat de grans companyies), té actualització en directe de cadenes de comentaris, i és petit (<4>~80kB</4>). Federar amb el sistema de xarxes ActivityPub forma part dels objectius del projecte. <5></5>Aquesta és una <6>versió beta molt prematura</6>, i actualment moltes de les característiques són trencades o falten. <7></7>Suggereix noves característiques o reporta errors <8>aquí</8>.<9></9>Fet amb <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
|
||||
"not_logged_in": "No has iniciat sessió.",
|
||||
"logged_in": "Has iniciat sessió.",
|
||||
"community_ban": "Has sigut expulsat d'aquesta comunitat.",
|
||||
"site_ban": "Has sigut expulsat d'aquest lloc.",
|
||||
"couldnt_create_comment": "No s'ha pogut crear el comentari.",
|
||||
"couldnt_like_comment": "No s'ha pogut donar m'agrada al comentari.",
|
||||
"couldnt_update_comment": "No s'ha pogut actualitzar el comentari.",
|
||||
"couldnt_save_comment": "No s'ha pogut guardar el comentari.",
|
||||
"no_comment_edit_allowed": "No tens permisos per a editar el comentari.",
|
||||
"no_post_edit_allowed": "No tens permisos per a editar la publicació.",
|
||||
"no_community_edit_allowed": "No tens permisos per a editar la comunitat.",
|
||||
"couldnt_find_community": "No s'ha pogut trobar la comunitat.",
|
||||
"couldnt_update_community": "No s'ha pogut actualitzar la comunitat.",
|
||||
"powered_by": "Funciona amb",
|
||||
"landing_0": "Lemmy és un <1>agregador de links</1> / alternativa a reddit, amb la intenció de funcionar al <2>fedivers</2>.<3></3>És allotjable per un mateix (sense necessitat de grans companyies), té actualització en directe de cadenes de comentaris, i és petit (<4>~80kB</4>). Federar amb el sistema de xarxes ActivityPub forma part dels objectius del projecte. <5></5>Aquesta és una <6>versió beta molt prematura</6>, i actualment moltes de les característiques són trencades o falten. <7></7>Suggereix noves característiques o reporta errors <8>aquí</8>.<9></9>Fet amb <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.",
|
||||
"not_logged_in": "No heu iniciat una sessió.",
|
||||
"logged_in": "Heu iniciat una sessió.",
|
||||
"community_ban": "Us han expulsat d’aquesta comunitat.",
|
||||
"site_ban": "Us han expulsat del lloc",
|
||||
"couldnt_create_comment": "No s’ha pogut crear el comentari.",
|
||||
"couldnt_like_comment": "No s’ha pogut donar «m’agrada» al comentari.",
|
||||
"couldnt_update_comment": "No s’ha pogut actualitzar el comentari.",
|
||||
"couldnt_save_comment": "No s’ha pogut desar el comentari.",
|
||||
"no_comment_edit_allowed": "No teniu permisos per a editar el comentari.",
|
||||
"no_post_edit_allowed": "No teniu permisos per a editar l’apunt.",
|
||||
"no_community_edit_allowed": "No teniu permisos per a editar la comunitat.",
|
||||
"couldnt_find_community": "No s’ha pogut trobar la comunitat.",
|
||||
"couldnt_update_community": "No s’ha pogut actualitzar la comunitat.",
|
||||
"community_already_exists": "Aquesta comunitat ja existeix.",
|
||||
"community_moderator_already_exists":
|
||||
"Aquest moderador de la comunitat ja existeix.",
|
||||
"community_follower_already_exists":
|
||||
"Aquest seguidor de la comunitat ja existeix.",
|
||||
"community_user_already_banned":
|
||||
"Aquest usuari de la comunitat ja fou expulsat.",
|
||||
"couldnt_create_post": "No s'ha pogut crear la publicació.",
|
||||
"couldnt_like_post": "No s'ha pogut donar m'agrada a la publicació.",
|
||||
"couldnt_find_post": "No s'ha pogut trobar la publicació.",
|
||||
"couldnt_get_posts": "No s'han pogut obtindre les publicacions.",
|
||||
"couldnt_update_post": "No s'ha pogut actualitzar la publicació.",
|
||||
"couldnt_save_post": "No s'ha pogut guardar la publicació.",
|
||||
"community_moderator_already_exists": "Aquest moderador de la comunitat ja existeix.",
|
||||
"community_follower_already_exists": "Aquest seguidor de la comunitat ja existeix.",
|
||||
"community_user_already_banned": "Aquest usuari de la comunitat ja fou expulsat.",
|
||||
"couldnt_create_post": "No s’ha pogut crear l’apunt.",
|
||||
"couldnt_like_post": "No s’ha pogut donar «m’agrada» a l’apunt.",
|
||||
"couldnt_find_post": "No s’ha pogut trobar l’apunt.",
|
||||
"couldnt_get_posts": "No s’han pogut recuperar els apunts",
|
||||
"couldnt_update_post": "No s’ha pogut actualitzar l’apunt",
|
||||
"couldnt_save_post": "No s’ha pogut desar l’apunt.",
|
||||
"no_slurs": "Prohibit insultar.",
|
||||
"not_an_admin": "No és un administrador.",
|
||||
"site_already_exists": "El lloc ja existeix.",
|
||||
"couldnt_update_site": "No s'ha pogut actualitzar el lloc.",
|
||||
"couldnt_find_that_username_or_email":
|
||||
"No s'ha pogut trobar aquest nom de usuari o correu electrònic.",
|
||||
"couldnt_update_site": "No s’ha pogut actualitzar el lloc.",
|
||||
"couldnt_find_that_username_or_email": "No s’ha pogut trobar aquest nom de usuari o adreça electrònica.",
|
||||
"password_incorrect": "Contrasenya incorrecta.",
|
||||
"passwords_dont_match": "Les contrasenyes no coincideixen.",
|
||||
"admin_already_created": "Ho sentim, ja hi ha un adminisitrador.",
|
||||
"user_already_exists": "L'usuari ja existeix.",
|
||||
"email_already_exists": "El correu ja és en ús.",
|
||||
"couldnt_update_user": "No s'ha pogut actualitzar l'usuari.",
|
||||
"system_err_login":
|
||||
"Error del sistema. Intenti tancar sessió i ingressar de nou.",
|
||||
"couldnt_create_private_message": "No s'ha pogut crear el missatge privat.",
|
||||
"no_private_message_edit_allowed":
|
||||
"Sense permisos per a editar el missatge privat.",
|
||||
"couldnt_update_private_message":
|
||||
"No s'ha pogut actualitzar el missatge privat."
|
||||
"admin_already_created": "Disculpeu; ja hi ha un adminisitrador.",
|
||||
"user_already_exists": "L’usuari ja existeix.",
|
||||
"email_already_exists": "L’adreça ja és en ús.",
|
||||
"couldnt_update_user": "No s’ha pogut actualitzar l’usuari.",
|
||||
"system_err_login": "Error del sistema. Intenti tancar sessió i ingressar de nou.",
|
||||
"couldnt_create_private_message": "No s’ha pogut crear el missatge privat.",
|
||||
"no_private_message_edit_allowed": "No teniu permisos per a editar el missatge privat.",
|
||||
"couldnt_update_private_message": "No s’ha pogut actualitzar el missatge privat.",
|
||||
"cross_posted_to": "publicat també a: ",
|
||||
"invalid_username": "El nom d’usuari no és vàlid.",
|
||||
"click_to_delete_picture": "Feu clic per a suprimir la imatge.",
|
||||
"picture_deleted": "S’ha suprimit la imatge.",
|
||||
"invalid_community_name": "El nom no és vàlid.",
|
||||
"sorting_help": "ajuda amb l’ordenament",
|
||||
"old": "Antic",
|
||||
"more": "més",
|
||||
"show_context": "Mostra el context",
|
||||
"upvote": "Dona un vot positiu",
|
||||
"number_of_upvotes": "{{count}} vot positiu",
|
||||
"number_of_upvotes_plural": "{{count}} vots positius",
|
||||
"downvote": "Dona un vot negatiu",
|
||||
"number_of_downvotes": "{{count}} vot negatiu",
|
||||
"number_of_downvotes_plural": "{{count}} vots negatius",
|
||||
"admin_settings": "Configuració administrativa",
|
||||
"site_config": "Configuració del lloc",
|
||||
"banned_users": "Usuaris expulsats",
|
||||
"support_on_liberapay": "Suport al Liberapay",
|
||||
"support_on_open_collective": "Suport a l’OpenCollective",
|
||||
"silver_sponsors": "Els patrocinadors «silver» són els que van comprometre’s a donar 40 $ al Lemmy.",
|
||||
"site_saved": "S’ha desat el lloc.",
|
||||
"couldnt_get_comments": "No s’han pogut recuperar els comentaris.",
|
||||
"post_title_too_long": "El títol de l’apunt és massa llarg.",
|
||||
"time": "Hora",
|
||||
"action": "Acció",
|
||||
"emoji_picker": "Selector d’emojis",
|
||||
"block_leaving": "Segur que voleu sortir?",
|
||||
"select_a_community": "Seleccioneu una comunitat"
|
||||
}
|
||||
|
|
74
ui/translations/es.json
vendored
74
ui/translations/es.json
vendored
|
@ -25,26 +25,26 @@
|
|||
"number_of_communities": "{{count}} Comunidad",
|
||||
"number_of_communities_plural": "{{count}} Comunidades",
|
||||
"community_reqs": "minúsculas, guión bajo, y sin espacios.",
|
||||
"create_private_message": "Crear Mensaje Privado",
|
||||
"send_secure_message": "Enviar Mensaje Seguro",
|
||||
"send_message": "Enviar Mensaje",
|
||||
"create_private_message": "Crear mensaje privado",
|
||||
"send_secure_message": "Enviar mensaje seguro",
|
||||
"send_message": "Enviar mensaje",
|
||||
"message": "Mensaje",
|
||||
"edit": "editar",
|
||||
"reply": "responder",
|
||||
"cancel": "Cancelar",
|
||||
"preview": "Previsualizar",
|
||||
"upload_image": "subir imagen",
|
||||
"upload_image": "cargar imagen",
|
||||
"avatar": "Avatar",
|
||||
"upload_avatar": "Subir Avatar",
|
||||
"show_avatars": "Ver Avatares",
|
||||
"formatting_help": "Ayuda de formato",
|
||||
"upload_avatar": "Cargar avatar",
|
||||
"show_avatars": "Mostrar avatares",
|
||||
"formatting_help": "ayuda de formato",
|
||||
"view_source": "ver fuente",
|
||||
"unlock": "desbloquear",
|
||||
"lock": "bloquear",
|
||||
"sticky": "fijado",
|
||||
"unsticky": "no fijado",
|
||||
"link": "link",
|
||||
"archive_link": "archivar link",
|
||||
"link": "enlace",
|
||||
"archive_link": "enlace de archivo",
|
||||
"mod": "moderador",
|
||||
"mods": "moderadores",
|
||||
"moderates": "Modera",
|
||||
|
@ -65,8 +65,8 @@
|
|||
"mark_as_unread": "marcar como no leído",
|
||||
"delete": "eliminar",
|
||||
"deleted": "eliminado por creador",
|
||||
"delete_account": "Eliminar Cuenta",
|
||||
"delete_account_confirm": "Advertencia: esta acción eliminará permanentemente toda tu información. Introduce tu contraseña para confirmar.",
|
||||
"delete_account": "Eliminar cuenta",
|
||||
"delete_account_confirm": "Advertencia: esta acción eliminará permanentemente toda su información. Introduzca su contraseña para confirmar.",
|
||||
"restore": "restaurar",
|
||||
"ban": "expulsar",
|
||||
"ban_from_site": "expulsar del sitio",
|
||||
|
@ -77,16 +77,16 @@
|
|||
"unsave": "descartar",
|
||||
"create": "crear",
|
||||
"creator": "creador",
|
||||
"username": "Nombre de Usuario",
|
||||
"email_or_username": "Correo o Usuario",
|
||||
"number_of_users": "{{count}} Usuario",
|
||||
"number_of_users_plural": "{{count}} Usuarios",
|
||||
"number_of_subscribers": "{{count}} Suscriptor",
|
||||
"number_of_subscribers_plural": "{{count}} Suscriptores",
|
||||
"number_of_points": "{{count}} Punto",
|
||||
"number_of_points_plural": "{{count}} Puntos",
|
||||
"number_online": "{{count}} Usuario En Línea",
|
||||
"number_online_plural": "{{count}} Usuarios En Línea",
|
||||
"username": "Nombre de usuario",
|
||||
"email_or_username": "Correo o usuario",
|
||||
"number_of_users": "{{count}} usuario",
|
||||
"number_of_users_plural": "{{count}} usuarios",
|
||||
"number_of_subscribers": "{{count}} suscriptor",
|
||||
"number_of_subscribers_plural": "{{count}} suscriptores",
|
||||
"number_of_points": "{{count}} punto",
|
||||
"number_of_points_plural": "{{count}} puntos",
|
||||
"number_online": "{{count}} usuario en línea",
|
||||
"number_online_plural": "{{count}} usuarios en línea",
|
||||
"name": "Nombre",
|
||||
"title": "Titulo",
|
||||
"category": "Categoría",
|
||||
|
@ -98,7 +98,7 @@
|
|||
"subscribed": "Suscrito",
|
||||
"prev": "Anterior",
|
||||
"next": "Siguiente",
|
||||
"sidebar": "Descripción de la comunidad",
|
||||
"sidebar": "Barra lateral",
|
||||
"sort_type": "Tipo de orden",
|
||||
"hot": "Popular",
|
||||
"new": "Nuevo",
|
||||
|
@ -109,7 +109,7 @@
|
|||
"all": "Todo",
|
||||
"top": "Mejor",
|
||||
"api": "API",
|
||||
"docs": "Docs",
|
||||
"docs": "Documentación",
|
||||
"inbox": "Buzón de entrada",
|
||||
"inbox_for": "Buzón de entrada para <1>{{user}}</1>",
|
||||
"mark_all_as_read": "marcar todo como leído",
|
||||
|
@ -161,11 +161,11 @@
|
|||
"no_results": "Sin resultados.",
|
||||
"setup": "Configurar",
|
||||
"lemmy_instance_setup": "Configuración de instancia de Lemmy",
|
||||
"setup_admin": "Configurar administrador del Sitio",
|
||||
"your_site": "tu sitio",
|
||||
"setup_admin": "Configurar administrador del sitio",
|
||||
"your_site": "su sitio",
|
||||
"modified": "modificado",
|
||||
"nsfw": "NSFW",
|
||||
"show_nsfw": "Mostrar contenido NSFW",
|
||||
"nsfw": "Para adultos",
|
||||
"show_nsfw": "Mostrar contenido para adultos",
|
||||
"theme": "Tema",
|
||||
"sponsors": "Patrocinadores",
|
||||
"sponsors_of_lemmy": "Patrocinadores de Lemmy",
|
||||
|
@ -186,7 +186,7 @@
|
|||
"from": "desde",
|
||||
"transfer_community": "transferir comunidad",
|
||||
"transfer_site": "transferir sitio",
|
||||
"are_you_sure": "¿Estás seguro?",
|
||||
"are_you_sure": "¿está seguro?",
|
||||
"yes": "sí",
|
||||
"no": "no",
|
||||
"powered_by": "Impulsado por",
|
||||
|
@ -234,7 +234,7 @@
|
|||
"action": "Acción",
|
||||
"more": "más",
|
||||
"cross_posted_to": "publicado también en: ",
|
||||
"sorting_help": "ayuda del orden",
|
||||
"sorting_help": "ayuda de ordenación",
|
||||
"upvote": "Voto Positivo",
|
||||
"number_of_upvotes": "{{count}} Voto Positivo",
|
||||
"number_of_upvotes_plural": "{{count}} Votos Positivos",
|
||||
|
@ -246,15 +246,15 @@
|
|||
"block_leaving": "¿Está seguro de que desea salir?",
|
||||
"show_context": "Mostrar contexto",
|
||||
"silver_sponsors": "Sponsors Plata son los que han dado $40 a Lemmy.",
|
||||
"site_config": "Configuración del Sitio",
|
||||
"banned_users": "Usuarios Baneados",
|
||||
"support_on_open_collective": "Dona en OpenCollective",
|
||||
"site_config": "Configuración del sitio",
|
||||
"banned_users": "Usuarios expulsados",
|
||||
"support_on_open_collective": "Apoyo en OpenCollective",
|
||||
"site_saved": "Sitio Guardado.",
|
||||
"emoji_picker": "Lista de emojis",
|
||||
"admin_settings": "Panel de Administración",
|
||||
"emoji_picker": "Selector de emoyis",
|
||||
"admin_settings": "Configuración administrativa",
|
||||
"select_a_community": "Selecciona una comunidad",
|
||||
"invalid_username": "Nombre de usuario inválido.",
|
||||
"invalid_community_name": "Nombre inválido.",
|
||||
"click_to_delete_picture": "Haz click para eliminar la imagen.",
|
||||
"picture_deleted": "Foto eliminada."
|
||||
"invalid_community_name": "Nombre no válido.",
|
||||
"click_to_delete_picture": "Pulse para eliminar la imagen.",
|
||||
"picture_deleted": "Imagen eliminada."
|
||||
}
|
||||
|
|
4
ui/translations/fr.json
vendored
4
ui/translations/fr.json
vendored
|
@ -156,7 +156,7 @@
|
|||
"body": "Texte",
|
||||
"copy_suggested_title": "copier le titre suggéré : {{title}}",
|
||||
"community": "Communauté",
|
||||
"expand_here": "Développer ici",
|
||||
"expand_here": "Ouvrir ici",
|
||||
"subscribe_to_communities": "S’abonner à quelques <1>communautés</1>.",
|
||||
"chat": "Chat",
|
||||
"recent_comments": "Commentaires récents",
|
||||
|
@ -244,7 +244,7 @@
|
|||
"sorting_help": "aide au tri",
|
||||
"upvote": "Voter pour",
|
||||
"show_context": "Afficher le contexte",
|
||||
"block_leaving": "Etes-vous sûr de vouloir partir ?",
|
||||
"block_leaving": "Êtes-vous sûr de vouloir partir ?",
|
||||
"number_of_upvotes": "{{count}} Votes pour",
|
||||
"number_of_upvotes_plural": "{{count}} Votes contre",
|
||||
"number_of_downvotes": "{{count}} Vote contre",
|
||||
|
|
4
ui/translations/sq.json
vendored
4
ui/translations/sq.json
vendored
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"remove_post": "Hiqe Postimin",
|
||||
"no_posts": "Nuk ka Postime.",
|
||||
"create_a_post": "Krijo Postim",
|
||||
"create_a_post": "Krijo një Postim",
|
||||
"create_post": "Krijo Postim",
|
||||
"posts": "Postime",
|
||||
"related_posts": "Këto postime mund të jenë të lidhura",
|
||||
"cross_posts": "Ky link është postuar edhe te:",
|
||||
"cross_posts": "Ky link është postuar gjithashtu në:",
|
||||
"cross_post": "shumë-postim",
|
||||
"cross_posted_to": "shumë-postuar në: ",
|
||||
"comments": "Komentet",
|
||||
|
|
123
ui/translations/sv.json
vendored
123
ui/translations/sv.json
vendored
|
@ -4,13 +4,15 @@
|
|||
"no_posts": "Inga inlägg.",
|
||||
"create_a_post": "Skriv ett inlägg",
|
||||
"create_post": "Skapa inlägg",
|
||||
"number_of_posts": "{{count}} inlägg",
|
||||
"number_of_posts": "{{count}} Inlägg",
|
||||
"number_of_posts_plural": "{{count}} Inlägg",
|
||||
"posts": "Inlägg",
|
||||
"related_posts": "Dessa inlägg kan vara relaterade",
|
||||
"cross_posts": "Den här länken har även publicerats i:",
|
||||
"cross_post": "tvärinlägg",
|
||||
"cross_post": "tvärposta",
|
||||
"comments": "Kommentarer",
|
||||
"number_of_comments": "{{count}} kommentarer",
|
||||
"number_of_comments": "{{count}} Kommentar",
|
||||
"number_of_comments_plural": "{{count}} Kommentarer",
|
||||
"remove_comment": "Radera kommentar",
|
||||
"communities": "Gemenskaper",
|
||||
"users": "Användare",
|
||||
|
@ -20,7 +22,8 @@
|
|||
"subscribed_to_communities": "Prenumererar på <1>gemenskaper</1>",
|
||||
"trending_communities": "Populära <1>gemenskaper</1>",
|
||||
"list_of_communities": "Lista över gemenskaper",
|
||||
"number_of_communities": "{{count}} gemenskaper",
|
||||
"number_of_communities": "{{count}} Gemenskap",
|
||||
"number_of_communities_plural": "{{count}} Gemenskaper",
|
||||
"community_reqs": "gemener, understreck och inga blanksteg.",
|
||||
"edit": "redigera",
|
||||
"reply": "svara",
|
||||
|
@ -46,33 +49,36 @@
|
|||
"remove_as_admin": "tag bort som administratör",
|
||||
"appoint_as_admin": "lägg till som administratör",
|
||||
"remove": "ta bort",
|
||||
"removed": "borttagen",
|
||||
"removed": "borttagen av moderator",
|
||||
"locked": "låst",
|
||||
"stickied": "fastnålad",
|
||||
"reason": "Anledning",
|
||||
"mark_as_read": "markera som läst",
|
||||
"mark_as_unread": "markera som oläst",
|
||||
"delete": "radera",
|
||||
"deleted": "raderad",
|
||||
"deleted": "raderad av skapare",
|
||||
"delete_account": "Ta bort konto",
|
||||
"delete_account_confirm":
|
||||
"Varning: den här åtgärden kommer radera alla dina data permanent. Är du säker?",
|
||||
"delete_account_confirm": "Varning: den här åtgärden kommer radera alla dina data permanent. Skriv in ditt lösenord för att bekräfta.",
|
||||
"restore": "återställ",
|
||||
"ban": "blockera",
|
||||
"ban_from_site": "blockera från webbplats",
|
||||
"unban": "ta bort blockering",
|
||||
"unban_from_site": "ta bort blockering från webbplats",
|
||||
"banned": "blocerad",
|
||||
"banned": "blockerad",
|
||||
"save": "spara",
|
||||
"unsave": "förkasta",
|
||||
"create": "skapa",
|
||||
"creator": "skapare",
|
||||
"username": "Användarnamn",
|
||||
"email_or_username": "E-postadress eller användarnamn",
|
||||
"number_of_users": "{{count}} användare",
|
||||
"number_of_subscribers": "{{count}} prenumeranter",
|
||||
"number_of_points": "{{count}} poäng",
|
||||
"number_online": "{{count}} användare inloggade",
|
||||
"number_of_users": "{{count}} Användare",
|
||||
"number_of_users_plural": "{{count}} Användare",
|
||||
"number_of_subscribers": "{{count}} Prenumerant",
|
||||
"number_of_subscribers_plural": "{{count}} Prenumeranter",
|
||||
"number_of_points": "{{count}} Poäng",
|
||||
"number_of_points_plural": "{{count}} Poäng",
|
||||
"number_online": "{{count}} Användare inloggad",
|
||||
"number_online_plural": "{{count}} Användare inloggade",
|
||||
"name": "Namn",
|
||||
"title": "Titel",
|
||||
"category": "Kategori",
|
||||
|
@ -108,8 +114,7 @@
|
|||
"login_sign_up": "Logga in eller skapa konto",
|
||||
"login": "Logga in",
|
||||
"sign_up": "Skapa konto",
|
||||
"notifications_error":
|
||||
"Din webbläsare har inte stöd för skrivbordsaviseringar. Testa Firefox eller Chrome.",
|
||||
"notifications_error": "Din webbläsare har inte stöd för skrivbordsaviseringar. Testa Firefox eller Chrome.",
|
||||
"unread_messages": "Olästa meddelanden",
|
||||
"password": "Lösenord",
|
||||
"verify_password": "Bekräfta lösenord",
|
||||
|
@ -135,11 +140,9 @@
|
|||
"theme": "Utseende",
|
||||
"sponsors": "Sponsorer",
|
||||
"sponsors_of_lemmy": "Lemmys sponsorer",
|
||||
"sponsor_message":
|
||||
"Lemmy är fri mjukvara med <1>öppen källkod</1>, vilket innebär att ingen reklam, vinstindrivning eller venturekapital förekommer, någonsin. Dina donationer går direkt till att stöda utvecklingen av projektet. Stort tack till följande personer:",
|
||||
"sponsor_message": "Lemmy är fri mjukvara med <1>öppen källkod</1>, vilket innebär att ingen reklam, vinstindrivning eller venturekapital förekommer, någonsin. Dina donationer går direkt till att stöda utvecklingen av projektet. Stort tack till följande personer:",
|
||||
"support_on_patreon": "Stöd på Patreon",
|
||||
"general_sponsors":
|
||||
"Allmänna sponsorer är dem som givit mellan 10 och 39\u00a0dollar till Lemmy.",
|
||||
"general_sponsors": "Allmänna sponsorer är de som donerat mellan 10 och 39 dollar till Lemmy.",
|
||||
"crypto": "Kryptovaluta",
|
||||
"bitcoin": "Bitcoin",
|
||||
"ethereum": "Ethereum",
|
||||
|
@ -154,16 +157,15 @@
|
|||
"yes": "ja",
|
||||
"no": "nej",
|
||||
"powered_by": "Drivs av",
|
||||
"landing_0":
|
||||
"Lemmy är en <1>länksamlare</1> och alternativ till reddit, ämnad att fungera i <2>Fediversumet</2>.<3></3>Lemmy kan drivas av vem som helst, har kommentarstrådar som updateras i realid och är mycket liten (<4>ca 80\u00a0kB</4>). Federering med ActivityPub-nätverket är planerat. <5></5>Detta är en <6>väldigt tidig betaversion</6> och många funktioner saknas därför eller är trasiga.<7></7>Föreslå nya funktioner eller anmäl buggar <8>här</8>.<9></9>Skapad i <10>Rust</10>, <11>Actix</11>, <12>Inferno</12> och <13>Typescript</13>.",
|
||||
"landing_0": "Lemmy är en <1>länksamlare</1> och alternativ till reddit, ämnad att fungera i <2>Fediversumet</2>.<3></3>Lemmy kan drivas av vem som helst, har kommentarstrådar som updateras i realid och är mycket liten (<4>ca 80 kB</4>). Federering med ActivityPub-nätverket är planerat. <5></5>Detta är en <6>väldigt tidig betaversion</6> och många funktioner saknas därför eller är trasiga.<7></7>Föreslå nya funktioner eller anmäl buggar <8>här</8>.<9></9>Skapad i <10>Rust</10>, <11>Actix</11>, <12>Inferno</12> och <13>Typescript</13>.",
|
||||
"not_logged_in": "Inte inloggad.",
|
||||
"community_ban": "Du har blockerats från den här gemenskapen.",
|
||||
"site_ban": "Du har blockerats från webbplatsen.",
|
||||
"site_ban": "Du har blockerats från webbplatsen",
|
||||
"couldnt_create_comment": "Kunde inte skapa kommentar.",
|
||||
"couldnt_like_comment": "Kunde inte gilla kommentar.",
|
||||
"couldnt_update_comment": "Kunde inte uppdatera kommentar.",
|
||||
"couldnt_save_comment": "Kunde inte spara kommentar.",
|
||||
"no_comment_edit_allowed": "Har inte behörighet att redigera komentar.",
|
||||
"no_comment_edit_allowed": "Har inte behörighet att redigera kommentar.",
|
||||
"no_post_edit_allowed": "Har inte behörighet att redigera inlägg.",
|
||||
"no_community_edit_allowed": "Har inte behörighet att redigera gemenskap.",
|
||||
"couldnt_find_community": "Kunde inte hitta gemenskap.",
|
||||
|
@ -175,19 +177,84 @@
|
|||
"couldnt_create_post": "Kunde inte skapa inlägg.",
|
||||
"couldnt_like_post": "Kunde inte gilla inlägg.",
|
||||
"couldnt_find_post": "Kunde inte hitta inlägg.",
|
||||
"couldnt_get_posts": "Kunde inte hämta inlägg.",
|
||||
"couldnt_update_post": "Kunde inte uppdatera inlägg.",
|
||||
"couldnt_get_posts": "Kunde inte hämta inlägg",
|
||||
"couldnt_update_post": "Kunde inte uppdatera inlägg",
|
||||
"couldnt_save_post": "Kunde inte spara inlägg.",
|
||||
"no_slurs": "Inga förolämpningar.",
|
||||
"not_an_admin": "Inte en administratör.",
|
||||
"site_already_exists": "Webbplatsen finns redan.",
|
||||
"couldnt_update_site": "Kunde inte uppdatera webbplats.",
|
||||
"couldnt_find_that_username_or_email":
|
||||
"Kunde inte hitta det användarnamnet eller e-postadressen.",
|
||||
"couldnt_find_that_username_or_email": "Kunde inte hitta det användarnamnet eller e-postadressen.",
|
||||
"password_incorrect": "Ogiltigt lösenord.",
|
||||
"passwords_dont_match": "Lösenorden stämmer inte överens.",
|
||||
"admin_already_created": "Beklagar, men det finns redan en administratör.",
|
||||
"user_already_exists": "Användaren finns redan.",
|
||||
"couldnt_update_user": "Kunde inte uppdatera användare.",
|
||||
"system_err_login": "Systemfel. Försök att logga ut och sedan in igen."
|
||||
"system_err_login": "Systemfel. Försök att logga ut och sedan in igen.",
|
||||
"invalid_community_name": "Ogiltigt namn.",
|
||||
"click_to_delete_picture": "Klicka för att ta bort bild.",
|
||||
"picture_deleted": "Bild borttagen.",
|
||||
"upload_avatar": "Ladda upp avatar",
|
||||
"enable_nsfw": "Aktivera NSFW",
|
||||
"sorting_help": "sorteringshjälp",
|
||||
"more": "mer",
|
||||
"avatar": "Avatar",
|
||||
"cross_posted_to": "Tvärpostat till: ",
|
||||
"send_secure_message": "Skicka säkert meddelande",
|
||||
"send_message": "Skicka meddelande",
|
||||
"message": "Meddelande",
|
||||
"create_private_message": "Skapa Privatmeddelande",
|
||||
"show_avatars": "Visa avatarer",
|
||||
"archive_link": "Arkivera länk",
|
||||
"admin_settings": "Administratörsinställningar",
|
||||
"site_config": "Webbplats konfiguration",
|
||||
"old": "Gammal",
|
||||
"banned_users": "Blockerade användare",
|
||||
"docs": "Dokumentation",
|
||||
"post_title_too_long": "Inläggstitel är för lång.",
|
||||
"replies": "Svar",
|
||||
"mentions": "Nämner",
|
||||
"message_sent": "Meddelande skickat",
|
||||
"messages": "Meddelanden",
|
||||
"old_password": "Gammalt lösenord",
|
||||
"reset_password_mail_sent": "Skicka e-post för att återställa ditt lösenord.",
|
||||
"forgot_password": "Glömt lösenord",
|
||||
"password_change": "Lösenordsbyte",
|
||||
"new_password": "Nytt lösenord",
|
||||
"no_email_setup": "Denna server har inte satt upp e-post korrekt.",
|
||||
"matrix_user_id": "Matrix-användare",
|
||||
"show_context": "Visa innehåll",
|
||||
"private_message_disclaimer": "Varning: Privata meddelanden på Lemmy är inte säkra. Vänligen skapa ett konto på <1>Riot.im</1> för att skicka säkra meddelanden.",
|
||||
"send_notifications_to_email": "Skicka aviseringar till E-post",
|
||||
"language": "Språk",
|
||||
"browser_default": "Webbläsarestandard",
|
||||
"downvotes_disabled": "Nedröstningar inaktiverat",
|
||||
"enable_downvotes": "Aktivera nedröstningar",
|
||||
"upvote": "Upprösta",
|
||||
"number_of_upvotes": "{{count}} Uppröst",
|
||||
"number_of_upvotes_plural": "{{count}} Uppröstningar",
|
||||
"downvote": "Nedrösta",
|
||||
"number_of_downvotes": "{{count}} Nedröst",
|
||||
"number_of_downvotes_plural": "{{count}} Nedröstningar",
|
||||
"open_registration": "Öppen registrering",
|
||||
"registration_closed": "Registrering stängd",
|
||||
"support_on_liberapay": "Stöd på Liberapay",
|
||||
"support_on_open_collective": "Stöd på OpenCollective",
|
||||
"donate_to_lemmy": "Donera till Lemmy",
|
||||
"donate": "Donera",
|
||||
"silver_sponsors": "Silversponsor är de som donerat 40 dollar till Lemmy.",
|
||||
"logged_in": "Inloggad.",
|
||||
"site_saved": "Webbplats sparad.",
|
||||
"couldnt_get_comments": "Kunde inte hämta kommentarer.",
|
||||
"action": "Åtgärd",
|
||||
"email_already_exists": "E-post finns redan.",
|
||||
"couldnt_create_private_message": "Kunde inte skapa privat meddelande.",
|
||||
"no_private_message_edit_allowed": "Inte tillåtet att redigera privata meddelanden.",
|
||||
"couldnt_update_private_message": "Kunde inte uppdatera privat meddelande.",
|
||||
"time": "Tid",
|
||||
"emoji_picker": "Emoji-väljare",
|
||||
"block_leaving": "Är du säker på att du vill lämna?",
|
||||
"select_a_community": "Välj en gemenskap",
|
||||
"from": "från",
|
||||
"invalid_username": "Ogiltigt användarnamn."
|
||||
}
|
||||
|
|
30
ui/translations/zh.json
vendored
30
ui/translations/zh.json
vendored
|
@ -14,7 +14,7 @@
|
|||
"create_a_community": "创建新社群",
|
||||
"create_community": "创建社群",
|
||||
"remove_community": "移除社群",
|
||||
"subscribed_to_communities": "订阅新 <1>社群</1>",
|
||||
"subscribed_to_communities": "订阅新<1>社群</1>",
|
||||
"trending_communities": "热门<1>社群</1>",
|
||||
"list_of_communities": "社群列表",
|
||||
"community_reqs": "小写字母、下划线(_)且不含空格。",
|
||||
|
@ -27,9 +27,9 @@
|
|||
"mod": "社群管理",
|
||||
"mods": "社群管理",
|
||||
"moderates": "监管",
|
||||
"remove_as_mod": "删除社群管理",
|
||||
"remove_as_mod": "删除社群管理权限",
|
||||
"appoint_as_mod": "任命为社群管理",
|
||||
"modlog": "审核记录",
|
||||
"modlog": "管理记录",
|
||||
"admin": "总管理员",
|
||||
"admins": "总管理员",
|
||||
"remove_as_admin": "移除管理员权限",
|
||||
|
@ -55,8 +55,8 @@
|
|||
"number_of_users": "{{count}} 名用户",
|
||||
"number_of_subscribers": "{{count}} 名订阅者",
|
||||
"number_of_points": "{{count}} 积分",
|
||||
"name": "名字",
|
||||
"title": "标题",
|
||||
"name": "名称",
|
||||
"title": "标语",
|
||||
"category": "分类",
|
||||
"subscribers": "订阅",
|
||||
"both": "全部",
|
||||
|
@ -66,11 +66,11 @@
|
|||
"subscribed": "已订阅",
|
||||
"prev": "上一页",
|
||||
"next": "下一页",
|
||||
"sidebar": "侧边栏",
|
||||
"sidebar": "介绍",
|
||||
"sort_type": "排序方式",
|
||||
"hot": "最热",
|
||||
"new": "最新",
|
||||
"top_day": "每日推荐",
|
||||
"top_day": "日",
|
||||
"week": "周",
|
||||
"month": "月",
|
||||
"year": "年",
|
||||
|
@ -82,7 +82,7 @@
|
|||
"mark_all_as_read": "标记所有已读",
|
||||
"type": "类型",
|
||||
"unread": "未读",
|
||||
"reply_sent": "回复发送",
|
||||
"reply_sent": "回复已发送",
|
||||
"search": "搜索",
|
||||
"overview": "个人中心",
|
||||
"view": "查看",
|
||||
|
@ -168,10 +168,10 @@
|
|||
"yes": "是",
|
||||
"no": "否",
|
||||
"logged_in": "已登录。",
|
||||
"message": "信息",
|
||||
"message": "消息",
|
||||
"create_private_message": "创建私信",
|
||||
"send_secure_message": "发送安全信息",
|
||||
"send_message": "发送信息",
|
||||
"send_secure_message": "发送安全消息",
|
||||
"send_message": "发送消息",
|
||||
"more": "更多",
|
||||
"preview": "预览",
|
||||
"upload_image": "上传图片",
|
||||
|
@ -184,7 +184,7 @@
|
|||
"sticky": "固定",
|
||||
"unsticky": "取消固定",
|
||||
"archive_link": "链接归档",
|
||||
"settings": "设定",
|
||||
"settings": "设置",
|
||||
"stickied": "已置顶",
|
||||
"delete_account": "删除账号",
|
||||
"delete_account_confirm": "警告!此操作将永久删除你的数据,请输入密码进行确认。",
|
||||
|
@ -201,7 +201,7 @@
|
|||
"replies": "回复",
|
||||
"number_online": "{{count}} 名在线用户",
|
||||
"mentions": "提到",
|
||||
"message_sent": "已发送信息",
|
||||
"message_sent": "消息已发送",
|
||||
"old_password": "当前密码",
|
||||
"forgot_password": "忘记密码",
|
||||
"reset_password_mail_sent": "发送邮件重置密码。",
|
||||
|
@ -223,7 +223,7 @@
|
|||
"open_registration": "开放注册",
|
||||
"registration_closed": "注册功能已关闭",
|
||||
"recent_comments": "最新评论",
|
||||
"by": "由",
|
||||
"by": " ",
|
||||
"transfer_community": "转让社群",
|
||||
"transfer_site": "站点转让",
|
||||
"post_title_too_long": "帖子标题过长。",
|
||||
|
@ -233,7 +233,7 @@
|
|||
"no_private_message_edit_allowed": "没有编辑私信的权限。",
|
||||
"couldnt_update_private_message": "无法更新私信。",
|
||||
"time": "时间",
|
||||
"action": "行动",
|
||||
"action": "动作",
|
||||
"block_leaving": "确定要离开吗?",
|
||||
"show_context": "显示上下文",
|
||||
"admin_settings": "管理员设置",
|
||||
|
|
Loading…
Reference in a new issue