Merge branch 'dev' into nutomic-ansible

This commit is contained in:
Dessalines 2020-01-31 23:01:19 -05:00
commit ecbf7e8f85
30 changed files with 613 additions and 102 deletions

27
README.md vendored
View File

@ -159,25 +159,26 @@ Lemmy is free, open-source software, meaning no advertising, monetizing, or vent
## Translations ## Translations
If you'd like to add translations, take a look a look at the [English translation file](ui/src/translations/en.ts). If you'd like to add translations, take a look at the [English translation file](ui/src/translations/en.ts).
- Languages supported: Catalan, (`ca`), English (`en`), Chinese (`zh`), Dutch (`nl`), Esperanto (`eo`), Finnish (`fi`), French (`fr`), Spanish (`es`), Swedish (`sv`), German (`de`), Russian (`ru`), Italian (`it`). - Languages supported: Catalan, (`ca`), Farsi (`fa`), English (`en`), Chinese (`zh`), Dutch (`nl`), Esperanto (`eo`), Finnish (`fi`), French (`fr`), Spanish (`es`), Swedish (`sv`), German (`de`), Russian (`ru`), Italian (`it`).
<!-- translations --> <!-- translations -->
lang | done | missing lang | done | missing
---- | ---- | ------- ---- | ---- | -------
ca | 100% | ca | 100% | old
de | 88% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,docs,message_sent,messages,old_password,matrix_user_id,private_message_disclaimer,send_notifications_to_email,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message de | 87% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,old,docs,message_sent,messages,old_password,matrix_user_id,private_message_disclaimer,send_notifications_to_email,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
eo | 76% | number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,donate_to_lemmy,donate,from,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message fa | 73% | cross_post,subscribed_to_communities,trending_communities,create_private_message,send_secure_message,send_message,message,mod,mods,moderates,remove_as_mod,appoint_as_mod,modlog,stickied,ban,ban_from_site,unban,unban_from_site,banned,number_of_subscribers,subscribers,both,saved,unsubscribe,subscribe,subscribed,old,api,docs,inbox,inbox_for,message_sent,notifications_error,messages,no_email_setup,matrix_user_id,private_message_disclaimer,url,body,copy_suggested_title,community,expand_here,subscribe_to_communities,theme,sponsor_message,general_sponsors,joined,by,to,from,landing_0,logged_in,community_moderator_already_exists,community_follower_already_exists,community_user_already_banned,no_slurs,admin_already_created,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
es | 100% | eo | 75% | number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,donate_to_lemmy,donate,from,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
fi | 100% | es | 100% | old
fr | 83% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message fi | 100% | old
it | 84% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,docs,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message fr | 83% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
nl | 93% | create_private_message,send_secure_message,send_message,message,message_sent,messages,matrix_user_id,private_message_disclaimer,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message it | 84% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,old,docs,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
ru | 72% | cross_posts,cross_post,number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,donate_to_lemmy,donate,monero,by,to,from,transfer_community,transfer_site,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message nl | 92% | create_private_message,send_secure_message,send_message,message,old,message_sent,messages,matrix_user_id,private_message_disclaimer,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
sv | 83% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message ru | 72% | cross_posts,cross_post,number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,donate_to_lemmy,donate,monero,by,to,from,transfer_community,transfer_site,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
zh | 70% | cross_posts,cross_post,users,number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,donate_to_lemmy,donate,monero,by,to,from,transfer_community,transfer_site,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message sv | 83% | create_private_message,send_secure_message,send_message,message,avatar,upload_avatar,show_avatars,archive_link,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,donate_to_lemmy,donate,from,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
zh | 70% | cross_posts,cross_post,users,number_of_communities,create_private_message,send_secure_message,send_message,message,preview,upload_image,avatar,upload_avatar,show_avatars,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,old,docs,replies,mentions,message_sent,messages,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,matrix_user_id,private_message_disclaimer,send_notifications_to_email,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,donate_to_lemmy,donate,monero,by,to,from,transfer_community,transfer_site,are_you_sure,yes,no,logged_in,email_already_exists,couldnt_create_private_message,no_private_message_edit_allowed,couldnt_update_private_message
<!-- translationsstop --> <!-- translationsstop -->
If you'd like to update this report, run: If you'd like to update this report, run:

View File

@ -11,7 +11,7 @@ services:
- lemmy_db:/var/lib/postgresql/data - lemmy_db:/var/lib/postgresql/data
restart: always restart: always
lemmy: lemmy:
image: dessalines/lemmy:v0.6.5 image: dessalines/lemmy:v0.6.7
ports: ports:
- "127.0.0.1:8536:8536" - "127.0.0.1:8536:8536"
restart: always restart: always

View File

@ -0,0 +1,25 @@
-- Drop the materialized / built views
drop view reply_view;
create view reply_view as
with closereply as (
select
c2.id,
c2.creator_id as sender_id,
c.creator_id as recipient_id
from comment c
inner join comment c2 on c.id = c2.parent_id
where c2.creator_id != c.creator_id
-- Do union where post is null
union
select
c.id,
c.creator_id as sender_id,
p.creator_id as recipient_id
from comment c, post p
where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
)
select cv.*,
closereply.recipient_id
from comment_view cv, closereply
where closereply.id = cv.id
;

View File

@ -0,0 +1,27 @@
-- https://github.com/dessalines/lemmy/issues/197
drop view reply_view;
-- 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
;

View File

@ -0,0 +1 @@
drop view user_mention_mview;

View File

@ -0,0 +1,67 @@
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.post_id,
ac.parent_id,
ac.content,
ac.removed,
um.read,
ac.published,
ac.updated,
ac.deleted,
ac.community_id,
ac.banned,
ac.banned_from_community,
ac.creator_name,
ac.creator_avatar,
ac.score,
ac.upvotes,
ac.downvotes,
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
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.post_id,
ac.parent_id,
ac.content,
ac.removed,
um.read,
ac.published,
ac.updated,
ac.deleted,
ac.community_id,
ac.banned,
ac.banned_from_community,
ac.creator_name,
ac.creator_avatar,
ac.score,
ac.upvotes,
ac.downvotes,
null as user_id,
null as my_vote,
null as saved,
um.recipient_id
from all_comment ac
left join user_mention um on um.comment_id = ac.id
;

View File

@ -1,4 +1,5 @@
#!/bin/sh #!/bin/bash
set -e
declare -a arr=( declare -a arr=(
"https://mastodon.social/" "https://mastodon.social/"

34
server/query_testing/api_benchmark.sh vendored Executable file
View File

@ -0,0 +1,34 @@
#!/bin/bash
set -e
# By default, this script runs against `http://127.0.0.1:8536`, but you can pass a different Lemmy instance,
# eg `./api_benchmark.sh "https://example.com"`.
DOMAIN=${1:-"http://127.0.0.1:8536"}
declare -a arr=(
"/api/v1/site"
"/api/v1/categories"
"/api/v1/modlog"
"/api/v1/search?q=test&type_=Posts&sort=Hot"
"/api/v1/community"
"/api/v1/community/list?sort=Hot"
"/api/v1/post/list?sort=Hot&type_=All"
)
## now loop through the above array
for path in "${arr[@]}"
do
URL="$DOMAIN$path"
printf "\n\n\n"
echo "testing $URL"
curl --show-error --fail --silent "$URL" >/dev/null
ab -c 64 -t 10 "$URL" > out.abtest
grep "Server Hostname:" out.abtest
grep "Document Path:" out.abtest
grep "Requests per second" out.abtest
grep "(mean, across all concurrent requests)" out.abtest
grep "Transfer rate:" out.abtest
echo "---"
done
rm *.abtest

View File

@ -1,4 +1,5 @@
#!/bin/sh #!/bin/bash
set -e
# Do the views first # Do the views first
@ -17,6 +18,15 @@ psql -qAt -U lemmy -f explain.sql > community_view.json
echo "explain (analyze, format json) select * from site_view limit 1" > explain.sql echo "explain (analyze, format json) select * from site_view limit 1" > explain.sql
psql -qAt -U lemmy -f explain.sql > site_view.json 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 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
grep "Execution Time" *.json grep "Execution Time" *.json
rm explain.sql rm explain.sql

View File

@ -3,7 +3,7 @@ use diesel::PgConnection;
use std::str::FromStr; use std::str::FromStr;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ListCategories; pub struct ListCategories {}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ListCategoriesResponse { pub struct ListCategoriesResponse {
@ -72,7 +72,7 @@ pub struct EditSite {
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct GetSite; pub struct GetSite {}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct SiteResponse { pub struct SiteResponse {

View File

@ -1,4 +1,3 @@
use super::user_mention_view::user_mention_view::BoxedQuery;
use super::*; use super::*;
use diesel::pg::Pg; use diesel::pg::Pg;
@ -31,6 +30,34 @@ table! {
} }
} }
table! {
user_mention_mview (id) {
id -> Int4,
user_mention_id -> Int4,
creator_id -> Int4,
post_id -> Int4,
parent_id -> Nullable<Int4>,
content -> Text,
removed -> Bool,
read -> Bool,
published -> Timestamp,
updated -> Nullable<Timestamp>,
deleted -> Bool,
community_id -> Int4,
banned -> Bool,
banned_from_community -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
score -> BigInt,
upvotes -> BigInt,
downvotes -> BigInt,
user_id -> Nullable<Int4>,
my_vote -> Nullable<Int4>,
saved -> Nullable<Bool>,
recipient_id -> Int4,
}
}
#[derive( #[derive(
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone, Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
)] )]
@ -63,7 +90,7 @@ pub struct UserMentionView {
pub struct UserMentionQueryBuilder<'a> { pub struct UserMentionQueryBuilder<'a> {
conn: &'a PgConnection, conn: &'a PgConnection,
query: BoxedQuery<'a, Pg>, query: super::user_mention_view::user_mention_mview::BoxedQuery<'a, Pg>,
for_user_id: i32, for_user_id: i32,
sort: &'a SortType, sort: &'a SortType,
unread_only: bool, unread_only: bool,
@ -73,9 +100,9 @@ pub struct UserMentionQueryBuilder<'a> {
impl<'a> UserMentionQueryBuilder<'a> { impl<'a> UserMentionQueryBuilder<'a> {
pub fn create(conn: &'a PgConnection, for_user_id: i32) -> Self { pub fn create(conn: &'a PgConnection, for_user_id: i32) -> Self {
use super::user_mention_view::user_mention_view::dsl::*; use super::user_mention_view::user_mention_mview::dsl::*;
let query = user_mention_view.into_boxed(); let query = user_mention_mview.into_boxed();
UserMentionQueryBuilder { UserMentionQueryBuilder {
conn, conn,
@ -109,7 +136,7 @@ impl<'a> UserMentionQueryBuilder<'a> {
} }
pub fn list(self) -> Result<Vec<UserMentionView>, Error> { pub fn list(self) -> Result<Vec<UserMentionView>, Error> {
use super::user_mention_view::user_mention_view::dsl::*; use super::user_mention_view::user_mention_mview::dsl::*;
let mut query = self.query; let mut query = self.query;

View File

@ -1 +1 @@
pub const VERSION: &str = "v0.6.5"; pub const VERSION: &str = "v0.6.7";

3
ui/package.json vendored
View File

@ -49,6 +49,7 @@
"fuse-box": "^3.1.3", "fuse-box": "^3.1.3",
"lint-staged": "^10.0.2", "lint-staged": "^10.0.2",
"sortpack": "^2.0.1", "sortpack": "^2.0.1",
"ts-node": "^8.6.2",
"ts-transform-classcat": "^0.0.2", "ts-transform-classcat": "^0.0.2",
"ts-transform-inferno": "^4.0.2", "ts-transform-inferno": "^4.0.2",
"typescript": "^3.7.5" "typescript": "^3.7.5"
@ -59,7 +60,7 @@
"engineStrict": true, "engineStrict": true,
"husky": { "husky": {
"hooks": { "hooks": {
"pre-commit": "ts-node translation_report.ts && git add ../README.md && cargo clippy --manifest-path ../server/Cargo.toml --all-targets --all-features -- -D warnings && lint-staged" "pre-commit": "yarn run ts-node translation_report.ts && git add ../README.md && cargo clippy --manifest-path ../server/Cargo.toml --all-targets --all-features -- -D warnings && lint-staged"
} }
}, },
"lint-staged": { "lint-staged": {

View File

@ -96,6 +96,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
className={`form-control ${this.state.previewMode && 'd-none'}`} className={`form-control ${this.state.previewMode && 'd-none'}`}
value={this.state.commentForm.content} value={this.state.commentForm.content}
onInput={linkEvent(this, this.handleCommentContentChange)} onInput={linkEvent(this, this.handleCommentContentChange)}
onPaste={linkEvent(this, this.handleImageUploadPaste)}
required required
disabled={this.props.disabled} disabled={this.props.disabled}
rows={2} rows={2}
@ -208,9 +209,22 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
i.props.onReplyCancel(); i.props.onReplyCancel();
} }
handleImageUploadPaste(i: CommentForm, event: any) {
let image = event.clipboardData.files[0];
if (image) {
i.handleImageUpload(i, image);
}
}
handleImageUpload(i: CommentForm, event: any) { handleImageUpload(i: CommentForm, event: any) {
let file: any;
if (event.target) {
event.preventDefault(); event.preventDefault();
let file = event.target.files[0]; file = event.target.files[0];
} else {
file = event;
}
const imageUploadUrl = `/pictshare/api/upload.php`; const imageUploadUrl = `/pictshare/api/upload.php`;
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);

View File

@ -11,6 +11,7 @@ import {
SortType, SortType,
Post, Post,
GetPostsForm, GetPostsForm,
GetCommunityForm,
ListingType, ListingType,
GetPostsResponse, GetPostsResponse,
CreatePostLikeResponse, CreatePostLikeResponse,
@ -98,11 +99,11 @@ export class Community extends Component<any, State> {
() => console.log('complete') () => console.log('complete')
); );
if (this.state.communityId) { let form: GetCommunityForm = {
WebSocketService.Instance.getCommunity(this.state.communityId); id: this.state.communityId ? this.state.communityId : null,
} else if (this.state.communityName) { name: this.state.communityName ? this.state.communityName : null,
WebSocketService.Instance.getCommunityByName(this.state.communityName); };
} WebSocketService.Instance.getCommunity(form);
} }
componentWillUnmount() { componentWillUnmount() {

View File

@ -38,6 +38,8 @@ enum UnreadType {
Messages, Messages,
} }
type ReplyType = Comment | PrivateMessageI;
interface InboxState { interface InboxState {
unreadOrAll: UnreadOrAll; unreadOrAll: UnreadOrAll;
unreadType: UnreadType; unreadType: UnreadType;
@ -186,7 +188,7 @@ export class Inbox extends Component<any, InboxState> {
} }
all() { all() {
let combined: Array<Comment | PrivateMessageI> = []; let combined: Array<ReplyType> = [];
combined.push(...this.state.replies); combined.push(...this.state.replies);
combined.push(...this.state.mentions); combined.push(...this.state.mentions);

View File

@ -138,7 +138,7 @@ export class Navbar extends Component<any, NavbarState> {
</li> </li>
<li className="nav-item"> <li className="nav-item">
<Link <Link
class="nav-link ml-2" class="nav-link"
to="/sponsors" to="/sponsors"
title={i18n.t('donate_to_lemmy')} title={i18n.t('donate_to_lemmy')}
> >

View File

@ -160,6 +160,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
class="form-control" class="form-control"
value={this.state.postForm.url} value={this.state.postForm.url}
onInput={linkEvent(this, this.handlePostUrlChange)} onInput={linkEvent(this, this.handlePostUrlChange)}
onPaste={linkEvent(this, this.handleImageUploadPaste)}
/> />
{this.state.suggestedTitle && ( {this.state.suggestedTitle && (
<div <div
@ -442,9 +443,22 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
i.setState(i.state); i.setState(i.state);
} }
handleImageUploadPaste(i: PostForm, event: any) {
let image = event.clipboardData.files[0];
if (image) {
i.handleImageUpload(i, image);
}
}
handleImageUpload(i: PostForm, event: any) { handleImageUpload(i: PostForm, event: any) {
let file: any;
if (event.target) {
event.preventDefault(); event.preventDefault();
let file = event.target.files[0]; file = event.target.files[0];
} else {
file = event;
}
const imageUploadUrl = `/pictshare/api/upload.php`; const imageUploadUrl = `/pictshare/api/upload.php`;
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);

View File

@ -23,6 +23,7 @@ import {
SearchType, SearchType,
SortType, SortType,
SearchForm, SearchForm,
GetPostForm,
SearchResponse, SearchResponse,
GetSiteResponse, GetSiteResponse,
GetCommunityResponse, GetCommunityResponse,
@ -84,7 +85,10 @@ export class Post extends Component<any, PostState> {
() => console.log('complete') () => console.log('complete')
); );
WebSocketService.Instance.getPost(postId); let form: GetPostForm = {
id: postId,
};
WebSocketService.Instance.getPost(form);
} }
componentWillUnmount() { componentWillUnmount() {
@ -231,6 +235,18 @@ export class Post extends Component<any, PostState> {
onChange={linkEvent(this, this.handleCommentSortChange)} onChange={linkEvent(this, this.handleCommentSortChange)}
/> />
</label> </label>
<label
className={`btn btn-sm btn-secondary pointer ${this.state
.commentSort === CommentSortType.Old && 'active'}`}
>
{i18n.t('old')}
<input
type="radio"
value={CommentSortType.Old}
checked={this.state.commentSort === CommentSortType.Old}
onChange={linkEvent(this, this.handleCommentSortChange)}
/>
</label>
</div> </div>
); );
} }
@ -313,6 +329,13 @@ export class Post extends Component<any, PostState> {
+a.comment.deleted - +b.comment.deleted || +a.comment.deleted - +b.comment.deleted ||
b.comment.published.localeCompare(a.comment.published) b.comment.published.localeCompare(a.comment.published)
); );
} else if (this.state.commentSort == CommentSortType.Old) {
tree.sort(
(a, b) =>
+a.comment.removed - +b.comment.removed ||
+a.comment.deleted - +b.comment.deleted ||
a.comment.published.localeCompare(b.comment.published)
);
} else if (this.state.commentSort == CommentSortType.Hot) { } else if (this.state.commentSort == CommentSortType.Hot) {
tree.sort( tree.sort(
(a, b) => (a, b) =>

2
ui/src/i18next.ts vendored
View File

@ -12,6 +12,7 @@ import { nl } from './translations/nl';
import { it } from './translations/it'; import { it } from './translations/it';
import { fi } from './translations/fi'; import { fi } from './translations/fi';
import { ca } from './translations/ca'; import { ca } from './translations/ca';
import { fa } from './translations/fa';
// https://github.com/nimbusec-oss/inferno-i18next/blob/master/tests/T.test.js#L66 // https://github.com/nimbusec-oss/inferno-i18next/blob/master/tests/T.test.js#L66
const resources = { const resources = {
@ -27,6 +28,7 @@ const resources = {
it, it,
fi, fi,
ca, ca,
fa,
}; };
function format(value: any, format: any, lng: any): any { function format(value: any, format: any, lng: any): any {

55
ui/src/interfaces.ts vendored
View File

@ -47,6 +47,7 @@ export enum CommentSortType {
Hot, Hot,
Top, Top,
New, New,
Old,
} }
export enum ListingType { export enum ListingType {
@ -248,6 +249,10 @@ export interface FollowCommunityForm {
auth?: string; auth?: string;
} }
export interface GetFollowedCommunitiesForm {
auth: string;
}
export interface GetFollowedCommunitiesResponse { export interface GetFollowedCommunitiesResponse {
communities: Array<CommunityUser>; communities: Array<CommunityUser>;
} }
@ -523,6 +528,12 @@ export interface CommunityForm {
auth?: string; auth?: string;
} }
export interface GetCommunityForm {
id?: number;
name?: string;
auth?: string;
}
export interface GetCommunityResponse { export interface GetCommunityResponse {
community: Community; community: Community;
moderators: Array<CommunityUser>; moderators: Array<CommunityUser>;
@ -572,6 +583,11 @@ export interface PostFormParams {
community?: string; community?: string;
} }
export interface GetPostForm {
id: number;
auth?: string;
}
export interface GetPostResponse { export interface GetPostResponse {
post: Post; post: Post;
comments: Array<Comment>; comments: Array<Comment>;
@ -759,6 +775,45 @@ export interface PrivateMessageResponse {
message: PrivateMessage; message: PrivateMessage;
} }
export type MessageType =
| EditPrivateMessageForm
| LoginForm
| RegisterForm
| CommunityForm
| FollowCommunityForm
| ListCommunitiesForm
| GetFollowedCommunitiesForm
| PostForm
| GetPostForm
| GetPostsForm
| GetCommunityForm
| CommentForm
| CommentLikeForm
| SaveCommentForm
| CreatePostLikeForm
| BanFromCommunityForm
| AddAdminForm
| AddModToCommunityForm
| TransferCommunityForm
| TransferSiteForm
| SaveCommentForm
| BanUserForm
| AddAdminForm
| GetUserDetailsForm
| GetRepliesForm
| GetUserMentionsForm
| EditUserMentionForm
| GetModlogForm
| SiteForm
| SearchForm
| UserSettingsForm
| DeleteAccountForm
| PasswordResetForm
| PasswordChangeForm
| PrivateMessageForm
| EditPrivateMessageForm
| GetPrivateMessagesForm;
type ResponseType = type ResponseType =
| SiteResponse | SiteResponse
| GetFollowedCommunitiesResponse | GetFollowedCommunitiesResponse

View File

@ -9,9 +9,12 @@ import {
CommentForm, CommentForm,
SaveCommentForm, SaveCommentForm,
CommentLikeForm, CommentLikeForm,
GetPostForm,
GetPostsForm, GetPostsForm,
CreatePostLikeForm, CreatePostLikeForm,
GetCommunityForm,
FollowCommunityForm, FollowCommunityForm,
GetFollowedCommunitiesForm,
GetUserDetailsForm, GetUserDetailsForm,
ListCommunitiesForm, ListCommunitiesForm,
GetModlogForm, GetModlogForm,
@ -35,6 +38,7 @@ import {
PrivateMessageForm, PrivateMessageForm,
EditPrivateMessageForm, EditPrivateMessageForm,
GetPrivateMessagesForm, GetPrivateMessagesForm,
MessageType,
} from '../interfaces'; } from '../interfaces';
import { webSocket } from 'rxjs/webSocket'; import { webSocket } from 'rxjs/webSocket';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -108,15 +112,15 @@ export class WebSocketService {
} }
public getFollowedCommunities() { public getFollowedCommunities() {
let data = { auth: UserService.Instance.auth }; let form: GetFollowedCommunitiesForm = { auth: UserService.Instance.auth };
this.subject.next( this.subject.next(
this.wsSendWrapper(UserOperation.GetFollowedCommunities, data) this.wsSendWrapper(UserOperation.GetFollowedCommunities, form)
); );
} }
public listCategories() { public listCategories() {
this.subject.next( this.subject.next(
this.wsSendWrapper(UserOperation.ListCategories, undefined) this.wsSendWrapper(UserOperation.ListCategories, {})
); );
} }
@ -125,19 +129,14 @@ export class WebSocketService {
this.subject.next(this.wsSendWrapper(UserOperation.CreatePost, postForm)); this.subject.next(this.wsSendWrapper(UserOperation.CreatePost, postForm));
} }
public getPost(postId: number) { public getPost(form: GetPostForm) {
let data = { id: postId, auth: UserService.Instance.auth }; this.setAuth(form, false);
this.subject.next(this.wsSendWrapper(UserOperation.GetPost, data)); this.subject.next(this.wsSendWrapper(UserOperation.GetPost, form));
} }
public getCommunity(communityId: number) { public getCommunity(form: GetCommunityForm) {
let data = { id: communityId, auth: UserService.Instance.auth }; this.setAuth(form, false);
this.subject.next(this.wsSendWrapper(UserOperation.GetCommunity, data)); this.subject.next(this.wsSendWrapper(UserOperation.GetCommunity, form));
}
public getCommunityByName(name: string) {
let data = { name: name, auth: UserService.Instance.auth };
this.subject.next(this.wsSendWrapper(UserOperation.GetCommunity, data));
} }
public createComment(commentForm: CommentForm) { public createComment(commentForm: CommentForm) {
@ -255,7 +254,7 @@ export class WebSocketService {
} }
public getSite() { public getSite() {
this.subject.next(this.wsSendWrapper(UserOperation.GetSite, undefined)); this.subject.next(this.wsSendWrapper(UserOperation.GetSite, {}));
} }
public search(form: SearchForm) { public search(form: SearchForm) {
@ -310,7 +309,7 @@ export class WebSocketService {
); );
} }
private wsSendWrapper(op: UserOperation, data: any) { private wsSendWrapper(op: UserOperation, data: MessageType) {
let send = { op: UserOperation[op], data: data }; let send = { op: UserOperation[op], data: data };
console.log(send); console.log(send);
return send; return send;

View File

@ -15,9 +15,9 @@ export const de = {
remove_comment: 'Kommentar löschen', remove_comment: 'Kommentar löschen',
communities: 'Communities', communities: 'Communities',
users: 'Benutzer', users: 'Benutzer',
create_a_community: 'Eine community anlegen', create_a_community: 'Eine Gemeinschaft anlegen',
create_community: 'Community anlegen', create_community: 'Gemeinschaft anlegen',
remove_community: 'Community entfernen', remove_community: 'Gemeinschaft entfernen',
subscribed_to_communities: 'Abonnierte <1>communities</1>', subscribed_to_communities: 'Abonnierte <1>communities</1>',
trending_communities: 'Trending <1>communities</1>', trending_communities: 'Trending <1>communities</1>',
list_of_communities: 'Liste von communities', list_of_communities: 'Liste von communities',
@ -36,17 +36,17 @@ export const de = {
unsticky: 'nicht haftend', unsticky: 'nicht haftend',
link: 'link', link: 'link',
archive_link: 'Archiv-Link', archive_link: 'Archiv-Link',
mod: 'mod', mod: 'Moderator',
mods: 'mods', mods: 'Moderatoren',
moderates: 'Moderiert', moderates: 'Moderiert',
settings: 'Einstellungen', settings: 'Einstellungen',
remove_as_mod: 'Als mod entfernen', remove_as_mod: 'Als Moderator entfernen',
appoint_as_mod: 'Zum mod ernennen', appoint_as_mod: 'Zum Moderator ernennen',
modlog: 'Modlog', modlog: 'Modlog',
admin: 'admin', admin: 'Administrator',
admins: 'admins', admins: 'Administratoren',
remove_as_admin: 'Als admin entfernen', remove_as_admin: 'Als Administrator entfernen',
appoint_as_admin: 'Zum admin ernennen', appoint_as_admin: 'Zum Administrator ernennen',
remove: 'entfernen', remove: 'entfernen',
removed: 'entfernt', removed: 'entfernt',
locked: 'gesperrt', locked: 'gesperrt',
@ -66,11 +66,11 @@ export const de = {
unban_from_site: 'Von der Seite entbannen', unban_from_site: 'Von der Seite entbannen',
banned: 'gesperrt', banned: 'gesperrt',
save: 'speichern', save: 'speichern',
unsave: 'unsave', unsave: 'nicht speichern',
create: 'anlegen', create: 'anlegen',
creator: 'Ersteller', creator: 'Ersteller',
username: 'Username', username: 'Benutzername',
email_or_username: 'Email oder Username', email_or_username: 'E-mail oder Username',
number_of_users: '{{count}} Benutzer', number_of_users: '{{count}} Benutzer',
number_of_subscribers: '{{count}} Abonnenten', number_of_subscribers: '{{count}} Abonnenten',
number_of_points: '{{count}} Punkte', number_of_points: '{{count}} Punkte',
@ -86,7 +86,7 @@ export const de = {
subscribed: 'Abonniert', subscribed: 'Abonniert',
prev: 'Zurück', prev: 'Zurück',
next: 'Weiter', next: 'Weiter',
sidebar: 'Sidebar', sidebar: 'Seitenleiste',
sort_type: 'Sortieren nach', sort_type: 'Sortieren nach',
hot: 'Hot', hot: 'Hot',
new: 'Neu', new: 'Neu',
@ -122,22 +122,22 @@ export const de = {
no_email_setup: "Dieser Server hat E-Mails nicht korrekt eingerichtet.", no_email_setup: "Dieser Server hat E-Mails nicht korrekt eingerichtet.",
login: 'Einloggen', login: 'Einloggen',
sign_up: 'Registrieren', sign_up: 'Registrieren',
email: 'Email', email: 'E-Mail',
optional: 'Optional', optional: 'optional',
expires: 'Ablaufdatum', expires: 'Ablaufdatum',
language: 'Sprache', language: 'Sprache',
browser_default: 'Standard-Browser', browser_default: 'Standard-Browser',
url: 'URL', url: 'URL',
body: 'Text', body: 'Text',
copy_suggested_title: 'Vorgeschlagenen Titel übernehmen: {{title}}', copy_suggested_title: 'Vorgeschlagenen Titel übernehmen: {{title}}',
community: 'Community', community: 'Gemeinschaft',
expand_here: 'Expand here', expand_here: 'hier erweitern',
subscribe_to_communities: 'Abonniere ein paar <1>communities</1>.', subscribe_to_communities: 'Abonniere ein paar <1>communities</1>.',
chat: 'Chat', chat: 'Chat',
recent_comments: 'Neueste Kommentare', recent_comments: 'Neueste Kommentare',
no_results: 'Keine Ergebnisse.', no_results: 'Keine Ergebnisse.',
setup: 'Setup', setup: 'Einrichten',
lemmy_instance_setup: 'Lemmy Instanz Setup', lemmy_instance_setup: 'Lemmy Instanz Einrichten',
setup_admin: 'Seiten Administrator konfigurieren', setup_admin: 'Seiten Administrator konfigurieren',
your_site: 'deine Seite', your_site: 'deine Seite',
modified: 'verändert', modified: 'verändert',
@ -151,7 +151,7 @@ export const de = {
support_on_patreon: 'Auf Patreon unterstützen', support_on_patreon: 'Auf Patreon unterstützen',
general_sponsors: general_sponsors:
'Allgemeine Sponsoren sind die, die zwischen $10 und $39 zu Lemmy beitragen.', 'Allgemeine Sponsoren sind die, die zwischen $10 und $39 zu Lemmy beitragen.',
crypto: 'Crypto', crypto: 'Kryptowährung',
bitcoin: 'Bitcoin', bitcoin: 'Bitcoin',
ethereum: 'Ethereum', ethereum: 'Ethereum',
monero: 'Monero', monero: 'Monero',
@ -159,16 +159,16 @@ export const de = {
joined: 'beigetreten', joined: 'beigetreten',
by: 'von', by: 'von',
to: 'bis', to: 'bis',
transfer_community: 'Transfer-Community', transfer_community: 'Gemeinschaft übertragen',
transfer_site: 'Transferseite', transfer_site: 'Transferseite',
are_you_sure: 'Bist du sicher?', are_you_sure: 'Bist du sicher?',
yes: 'Ja', yes: 'Ja',
no: 'Nein', no: 'Nein',
powered_by: 'Bereitgestellt durch', powered_by: 'Bereitgestellt durch',
landing_0: landing_0:
'Lemmy ist ein <1>Link Aggregator</1> / Reddit Alternative im <2>Fediverse</2>.<3></3>Es ist selbst-hostbar, hat live-updates von Kommentar-threads und ist winzig (<4>~80kB</4>). Federation in das ActivityPub Netzwerk ist geplant. <5></5>Dies ist eine <6>sehr frühe Beta Version</6>, und viele Features funktionieren zurzeit nicht richtig oder fehlen. <7></7>Schlage neue Features vor oder melde Bugs <8>hier.</8><9></9>Gebaut mit <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.', 'Lemmy ist ein <1>Link-Aggregator</1> / Reddit Alternative im <2>Fediverse</2>.<3></3>Es ist selbst-hostbar, hat live-updates von Kommentar-threads und ist winzig (<4>~80kB</4>). Federation in das ActivityPub Netzwerk ist geplant. <5></5>Dies ist eine <6>sehr frühe Beta Version</6>, und viele Features funktionieren zurzeit nicht richtig oder fehlen. <7></7>Schlage neue Features vor oder melde Bugs <8>hier.</8><9></9>Gebaut mit <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.',
not_logged_in: 'Nicht eingeloggt.', not_logged_in: 'Nicht eingeloggt.',
community_ban: 'Du wurdest von dieser Community gebannt.', community_ban: 'Du wurdest von dieser Gemeinschaft gebannt.',
site_ban: 'Du wurdest von dieser Seite gebannt', site_ban: 'Du wurdest von dieser Seite gebannt',
couldnt_create_comment: 'Konnte Kommentar nicht anlegen.', couldnt_create_comment: 'Konnte Kommentar nicht anlegen.',
couldnt_like_comment: 'Konnte nicht liken.', couldnt_like_comment: 'Konnte nicht liken.',
@ -176,14 +176,14 @@ export const de = {
couldnt_save_comment: 'Konnte Kommentar nicht speichern.', couldnt_save_comment: 'Konnte Kommentar nicht speichern.',
no_comment_edit_allowed: 'Keine Erlaubnis Kommentar zu editieren.', no_comment_edit_allowed: 'Keine Erlaubnis Kommentar zu editieren.',
no_post_edit_allowed: 'Keine Erlaubnis Beitrag zu editieren.', no_post_edit_allowed: 'Keine Erlaubnis Beitrag zu editieren.',
no_community_edit_allowed: 'Keine Erlaubnis Community zu editieren.', no_community_edit_allowed: 'Keine Erlaubnis Gemeinschaft zu editieren.',
couldnt_find_community: 'Konnte Community nicht finden.', couldnt_find_community: 'Konnte Gemeinschaft nicht finden.',
couldnt_update_community: 'Konnte Community nicht aktualisieren.', couldnt_update_community: 'Konnte Gemeinschaft nicht aktualisieren.',
community_already_exists: 'Community existiert bereits.', community_already_exists: 'Gemeinschaft existiert bereits.',
community_moderator_already_exists: community_moderator_already_exists:
'Community Moderator existiert bereits.', 'Gemeinschaft Moderator existiert bereits.',
community_follower_already_exists: 'Community Follower existiert bereits.', community_follower_already_exists: 'Gemeinschaft Follower existiert bereits.',
community_user_already_banned: 'Community Nutzer schon gebannt.', community_user_already_banned: 'Gemeinschaft Nutzer schon gebannt.',
couldnt_create_post: 'Konnte Beitrag nicht anlegen.', couldnt_create_post: 'Konnte Beitrag nicht anlegen.',
couldnt_like_post: 'Konnte Beitrag nicht liken.', couldnt_like_post: 'Konnte Beitrag nicht liken.',
couldnt_find_post: 'Konnte Beitrag nicht finden.', couldnt_find_post: 'Konnte Beitrag nicht finden.',

View File

@ -97,6 +97,7 @@ export const en = {
sort_type: 'Sort type', sort_type: 'Sort type',
hot: 'Hot', hot: 'Hot',
new: 'New', new: 'New',
old: 'Old',
top_day: 'Top day', top_day: 'Top day',
week: 'Week', week: 'Week',
month: 'Month', month: 'Month',

169
ui/src/translations/fa.ts vendored Normal file
View File

@ -0,0 +1,169 @@
export const fa = {
translation: {
post: 'مطلب',
remove_post: 'حذف مطلب',
no_posts: 'بدون مطلب.',
create_a_post: 'ایجاد یک مطلب',
create_post: 'ایجاد مطلب',
number_of_posts: '{{count}} مطلب',
posts: 'مطالب',
related_posts: 'این مطالب ممکن است مرتبط باشند',
cross_posts: 'این پیوند در اینجا هم منتشر شده:',
comments: 'نظرات',
number_of_comments: '{{count}} نظر',
remove_comment: 'حذف نظر',
communities: 'جوامع',
users: 'کاربران',
create_a_community: 'ایجاد یک جامعه جدید',
create_community: 'ایجاد جامعه',
remove_community: 'حذف جامعه',
list_of_communities: 'فهرست جوامع',
number_of_communities: '{{count}} جامعه',
community_reqs: 'حروف کوچک, زیرخط, و بدون فاصله.',
edit: 'ویرایش',
reply: 'پاسخ',
cancel: 'لغو',
preview: 'پیش‌نمایش',
upload_image: 'بارگذاری تصویر',
avatar: 'آواتار',
upload_avatar: 'بارگذاری آواتار',
show_avatars: 'نمایش آواتارها',
formatting_help: 'راهنمای قالب‌بندی',
view_source: 'نمایش منبع',
unlock: 'بازکردن قفل',
lock: 'قفل کردن',
sticky: 'چسبان',
unsticky: 'غیرچسبان',
link: 'پیوند',
archive_link: 'بایگاهی پیوند',
settings: 'تنظیمات',
admin: 'مدیر',
admins: 'مدیران',
remove_as_admin: 'حذف به عنوان مدیر',
appoint_as_admin: 'انتصاب به عنوان مدیر',
remove: 'حذف',
removed: 'حذف شد',
locked: 'قفل شد',
reason: 'دلیل',
mark_as_read: 'علامت‌گذاری به عنوان خوانده شده',
mark_as_unread: 'علامت‌گذاری به عنوان خوانده نشده',
delete: 'پاک کردن',
deleted: 'پاک شد',
delete_account: 'پاک کردن حساب',
delete_account_confirm:
'هشدار: این کنش، تمام اطلاعات شما را برای همیشه پاک می‌کند. برای تایید، گذرواژه خود را وارد کنید.',
restore: 'بازگردانی',
save: 'ذخیره',
unsave: 'عدم ذخیره',
create: 'ایجاد',
creator: 'سازنده',
username: 'نام‌کاربری',
email_or_username: 'رایانامه یا نام‌کاربری',
number_of_users: '{{count}} کاربر',
number_of_points: '{{count}} امتیاز',
number_online: '{{count}} کاربر برخط',
name: 'نام',
title: 'عنوان',
category: 'دسته‌بندی',
prev: 'پیش',
next: 'بعد',
sidebar: 'نوار کناری',
sort_type: 'نوع ترتیب',
hot: 'داغ',
new: 'تازه',
top_day: 'بهترین‌های روز',
week: 'هفته',
month: 'ماه',
year: 'سال',
all: 'همه',
top: 'بالاترین',
mark_all_as_read: 'علامت زدن همه به عنوان خوانده شده',
type: 'نوع',
unread: 'خوانده‌نشده',
replies: 'پاسخ‌ها',
mentions: 'اشاره‌ها',
reply_sent: 'پاسخ فرستاده شد',
search: 'جستجو',
overview: 'دید کلی',
view: 'نما',
logout: 'خروج',
login_sign_up: 'ورود / نام‌نویسی',
login: 'ورود',
sign_up: 'نام‌نویسی',
unread_messages: 'پیام‌های خوانده نشده',
password: 'گذرواژه',
verify_password: 'تایید گذرواژه',
old_password: 'پسورد پیشین',
forgot_password: 'گذرواژه را فراموش کرده‌ام',
reset_password_mail_sent: 'رایانامه‌ای برای بازنشانی گذرواژه فرستاده شد.',
password_change: 'تغییر گذرواژه',
new_password: 'گذرواژه جدید',
email: 'رایانامه',
send_notifications_to_email: 'فرستادن اعلانات به رایانامه',
optional: 'انتخابی',
expires: 'منقضی شود',
language: 'زبان',
browser_default: 'پیش‌فرض مرورگر',
downvotes_disabled: 'رای پایین غیرفعال است',
enable_downvotes: 'فعال‌سازی رای پایین',
open_registration: 'باز کردن نام‌نویسی',
registration_closed: 'نام‌نویسی بسته است',
enable_nsfw: 'فعال‌سازی NSFW',
chat: 'گپ',
recent_comments: 'نظرات اخیر',
no_results: 'بدون نتیجه.',
setup: 'نصب',
lemmy_instance_setup: 'نصب نمونهٔ لمی',
setup_admin: 'نصب مدیریت پایگاه',
your_site: 'پایگاه شما',
modified: 'تغییر یافت',
nsfw: 'NSFW',
show_nsfw: 'نمایش محتوای NSFW',
sponsors: 'حامیان',
sponsors_of_lemmy: 'حامیان لمی',
support_on_patreon: 'حمایت روی Patreon',
donate_to_lemmy: 'اعطای اعانه به لمی',
donate: 'اعانه',
crypto: 'رمزارز',
bitcoin: 'بیت‌کوین',
ethereum: 'اتریوم',
monero: 'مونرو',
code: 'کد',
transfer_community: 'انتقال جامعه',
transfer_site: 'انتقال پایگاه',
are_you_sure: 'مطمئنید؟',
yes: 'بله',
no: 'خیر',
powered_by: 'نیرو گرفته از',
not_logged_in: 'وارد نشده‌اید.',
community_ban: 'فعالیت شما در این جامعه ممنوع شده است.',
site_ban: 'فعالیت شما در این پایگاه ممنوع شده است',
couldnt_create_comment: 'ناتوانی در ایجاد نظر.',
couldnt_like_comment: 'ناتوانی در پسنیدن نظر.',
couldnt_update_comment: 'ناتوانی در به‌روزرسانی نظر.',
couldnt_save_comment: 'ناتوانی در ذخیره نظر.',
no_comment_edit_allowed: 'مجاز به ویرایش نظر نیستید.',
no_post_edit_allowed: 'مجاز به ویرایش مطلب نیستید.',
no_community_edit_allowed: 'مجاز به ویرایش جامعه نیستید.',
couldnt_find_community: 'ناتوانی در یافتن جامعه.',
couldnt_update_community: 'ناتوانی در به‌روزرسانی جامعه.',
community_already_exists: 'این جامعه از قبل وجود داشته است.',
couldnt_create_post: 'ناتوانی در ایجاد مطلب.',
couldnt_like_post: 'ناتوانی در پسندیدن مطلب.',
couldnt_find_post: 'ناتوانی در یافتن مطلب.',
couldnt_get_posts: 'ناتوانی در دریافت مطالب',
couldnt_update_post: 'ناتوای در به‌روزرسانی مطلب',
couldnt_save_post: 'ناتوانی در ذخیره مطلب.',
not_an_admin: 'مدیر نیستید.',
site_already_exists: 'این پایگاه از قبل وجود داشته است.',
couldnt_update_site: 'ناتوانی در به‌روزرسانی پایگاه.',
couldnt_find_that_username_or_email:
'ناتوانی در یافتن این نام کاربری یا رایانامه.',
password_incorrect: 'گذرواژه نادرست.',
passwords_dont_match: 'گذرواژه‌ها با هم منطبق نیستند.',
user_already_exists: 'این کاربر از قبل وجود دارد.',
email_already_exists: 'این رایانامه از قبل وجود دارد.',
couldnt_update_user: 'ناتوانی در به‌روزرسانی کاربر.',
system_err_login: 'خطای سامانه. سعی کنید خارج شده و دوباره وارد شوید.',
},
};

View File

@ -25,14 +25,14 @@ export const zh = {
unlock: '解锁', unlock: '解锁',
lock: '加锁', lock: '加锁',
link: '链接', link: '链接',
mod: 'mod', mod: '监管人',
mods: 'mods', mods: '监管人',
moderates: 'Moderates', moderates: '监管',
remove_as_mod: 'remove as mod', remove_as_mod: '添加监管人',
appoint_as_mod: 'appoint as mod', appoint_as_mod: '移除监管人',
modlog: 'Modlog', modlog: '监管记录',
admin: 'admin', admin: '管理权限',
admins: 'admins', admins: '管理权限',
remove_as_admin: '移除管理权限', remove_as_admin: '移除管理权限',
appoint_as_admin: '添加管理权限', appoint_as_admin: '添加管理权限',
remove: '移除', remove: '移除',
@ -77,7 +77,7 @@ export const zh = {
year: '年', year: '年',
all: '所有', all: '所有',
top: '最热', top: '最热',
api: 'API', api: '应用程式介面',
inbox: '收件箱', inbox: '收件箱',
inbox_for: '<1>{{user}}</1> 收件箱', inbox_for: '<1>{{user}}</1> 收件箱',
mark_all_as_read: '标记所有已读', mark_all_as_read: '标记所有已读',
@ -98,7 +98,7 @@ export const zh = {
email: '邮箱', email: '邮箱',
optional: '选项', optional: '选项',
expires: '过期', expires: '过期',
url: 'URL', url: '网址',
body: '内容', body: '内容',
copy_suggested_title: '复制建议的标题: {{title}}', copy_suggested_title: '复制建议的标题: {{title}}',
community: '节点', community: '节点',
@ -111,11 +111,11 @@ export const zh = {
setup_admin: '设置管理员', setup_admin: '设置管理员',
your_site: '你的站点', your_site: '你的站点',
modified: '修改', modified: '修改',
sponsors: 'Sponsors', sponsors: '发起人',
sponsors_of_lemmy: 'Sponsors of Lemmy', sponsors_of_lemmy: 'Lemmy 的发起人',
sponsor_message: sponsor_message:
'Lemmy is free, <1>open-source</1> software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. Thank you to the following people:', 'Lemmy is free, <1>open-source</1> software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. Thank you to the following people:',
support_on_patreon: 'Support on Patreon', support_on_patreon: '在 Patreon 赞助',
general_sponsors: general_sponsors:
'General Sponsors are those that pledged $10 to $39 to Lemmy.', 'General Sponsors are those that pledged $10 to $39 to Lemmy.',
crypto: '加密', crypto: '加密',
@ -139,8 +139,8 @@ export const zh = {
couldnt_find_community: '不能找到节点.', couldnt_find_community: '不能找到节点.',
couldnt_update_community: '不能更新节点.', couldnt_update_community: '不能更新节点.',
community_already_exists: '节点已存在.', community_already_exists: '节点已存在.',
community_moderator_already_exists: '节点 moderator 已存在.', community_moderator_already_exists: '节点监管人已存在.',
community_follower_already_exists: '节点 follower 已存在.', community_follower_already_exists: '节点追随者已存在.',
community_user_already_banned: '节点用户已禁止.', community_user_already_banned: '节点用户已禁止.',
couldnt_create_post: '不能创建帖子.', couldnt_create_post: '不能创建帖子.',
couldnt_like_post: '不能收藏帖子.', couldnt_like_post: '不能收藏帖子.',

4
ui/src/utils.ts vendored
View File

@ -9,6 +9,7 @@ import 'moment/locale/nl';
import 'moment/locale/it'; import 'moment/locale/it';
import 'moment/locale/fi'; import 'moment/locale/fi';
import 'moment/locale/ca'; import 'moment/locale/ca';
import 'moment/locale/fa';
import { import {
UserOperation, UserOperation,
@ -258,6 +259,7 @@ export const languages = [
{ code: 'eo', name: 'Esperanto' }, { code: 'eo', name: 'Esperanto' },
{ code: 'es', name: 'Español' }, { code: 'es', name: 'Español' },
{ code: 'de', name: 'Deutsch' }, { code: 'de', name: 'Deutsch' },
{ code: 'fa', name: 'فارسی' },
{ code: 'zh', name: '中文' }, { code: 'zh', name: '中文' },
{ code: 'fi', name: 'Suomi' }, { code: 'fi', name: 'Suomi' },
{ code: 'fr', name: 'Français' }, { code: 'fr', name: 'Français' },
@ -306,6 +308,8 @@ export function getMomentLanguage(): string {
lang = 'fi'; lang = 'fi';
} else if (lang.startsWith('ca')) { } else if (lang.startsWith('ca')) {
lang = 'ca'; lang = 'ca';
} else if (lang.startsWith('fa')) {
lang = 'fa';
} else { } else {
lang = 'en'; lang = 'en';
} }

2
ui/src/version.ts vendored
View File

@ -1 +1 @@
export const version: string = 'v0.6.5'; export const version: string = 'v0.6.7';

View File

@ -2,6 +2,7 @@ import { en } from './src/translations/en';
import { eo } from './src/translations/eo'; import { eo } from './src/translations/eo';
import { es } from './src/translations/es'; import { es } from './src/translations/es';
import { de } from './src/translations/de'; import { de } from './src/translations/de';
import { fa } from './src/translations/fa';
import { zh } from './src/translations/zh'; import { zh } from './src/translations/zh';
import { fr } from './src/translations/fr'; import { fr } from './src/translations/fr';
import { sv } from './src/translations/sv'; import { sv } from './src/translations/sv';
@ -15,6 +16,7 @@ import fs from 'fs';
const files = [ const files = [
{ t: ca, n: 'ca' }, { t: ca, n: 'ca' },
{ t: de, n: 'de' }, { t: de, n: 'de' },
{ t: fa, n: 'fa' },
{ t: eo, n: 'eo' }, { t: eo, n: 'eo' },
{ t: es, n: 'es' }, { t: es, n: 'es' },
{ t: fi, n: 'fi' }, { t: fi, n: 'fi' },

33
ui/yarn.lock vendored
View File

@ -382,6 +382,11 @@ app-root-path@^2.0.1:
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a"
integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==
arg@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.2.tgz#e70c90579e02c63d80e3ad4e31d8bfdb8bd50064"
integrity sha512-+ytCkGcBtHZ3V2r2Z06AncYO8jz46UEamcspGoU8lHcEbpn6J77QK0vdWvChsclg/tM5XIJC5tnjmPp7Eq6Obg==
argparse@^1.0.7: argparse@^1.0.7:
version "1.0.10" version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@ -1036,6 +1041,11 @@ destroy@~1.0.4:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
doctrine@1.5.0: doctrine@1.5.0:
version "1.5.0" version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@ -2910,6 +2920,11 @@ loose-envify@^1.2.0, loose-envify@^1.4.0:
dependencies: dependencies:
js-tokens "^3.0.0 || ^4.0.0" js-tokens "^3.0.0 || ^4.0.0"
make-error@^1.1.1:
version "1.3.5"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==
map-cache@^0.2.2: map-cache@^0.2.2:
version "0.2.2" version "0.2.2"
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
@ -4123,7 +4138,7 @@ source-map-resolve@^0.5.0:
source-map-url "^0.4.0" source-map-url "^0.4.0"
urix "^0.1.0" urix "^0.1.0"
source-map-support@~0.5.12: source-map-support@^0.5.6, source-map-support@~0.5.12:
version "0.5.16" version "0.5.16"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
@ -4477,6 +4492,17 @@ tributejs@^4.1.1:
resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-4.1.1.tgz#f169a4ad12e485241140ec1ab987b460950c974c" resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-4.1.1.tgz#f169a4ad12e485241140ec1ab987b460950c974c"
integrity sha512-jc+PcaiNzMjCn2LAQb3i4ic94EsSfLW8Jlk1sK2cb6hLcZFalU9ThcF8rxuKkTUKv1GIvTwN8XseLzCXLxB4lw== integrity sha512-jc+PcaiNzMjCn2LAQb3i4ic94EsSfLW8Jlk1sK2cb6hLcZFalU9ThcF8rxuKkTUKv1GIvTwN8XseLzCXLxB4lw==
ts-node@^8.6.2:
version "8.6.2"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.6.2.tgz#7419a01391a818fbafa6f826a33c1a13e9464e35"
integrity sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg==
dependencies:
arg "^4.1.0"
diff "^4.0.1"
make-error "^1.1.1"
source-map-support "^0.5.6"
yn "3.1.1"
ts-transform-classcat@^0.0.2: ts-transform-classcat@^0.0.2:
version "0.0.2" version "0.0.2"
resolved "https://registry.yarnpkg.com/ts-transform-classcat/-/ts-transform-classcat-0.0.2.tgz#2386c9418f3a7c1f03261ff51225b70d0a7664fb" resolved "https://registry.yarnpkg.com/ts-transform-classcat/-/ts-transform-classcat-0.0.2.tgz#2386c9418f3a7c1f03261ff51225b70d0a7664fb"
@ -4769,3 +4795,8 @@ yaml@^1.7.2:
integrity sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw== integrity sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==
dependencies: dependencies:
"@babel/runtime" "^7.6.3" "@babel/runtime" "^7.6.3"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==