summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDessalines <tyhou13@gmx.com>2019-08-23 19:40:41 -0700
committerDessalines <tyhou13@gmx.com>2019-08-23 19:40:41 -0700
commit3561ef93a08742c74f36609754b87f2cd4a84e1c (patch)
treed6a1a5a6823bfee68a3e6bbbea9031f597bdb79b
parentd5b9ba724dbab8ac1b17ca52d3f85309e5f22497 (diff)
Adding Community and Site transfer
- Fixes #139
-rw-r--r--README.md4
-rw-r--r--server/src/api/community.rs117
-rw-r--r--server/src/api/mod.rs2
-rw-r--r--server/src/api/post.rs6
-rw-r--r--server/src/api/site.rs80
-rw-r--r--server/src/api/user.rs6
-rw-r--r--server/src/db/comment.rs7
-rw-r--r--server/src/db/community.rs10
-rw-r--r--server/src/websocket/server.rs10
-rw-r--r--ui/src/components/comment-node.tsx45
-rw-r--r--ui/src/components/post.tsx13
-rw-r--r--ui/src/interfaces.ts13
-rw-r--r--ui/src/services/WebSocketService.ts12
-rw-r--r--ui/src/translations/en.ts2
14 files changed, 314 insertions, 13 deletions
diff --git a/README.md b/README.md
index 915df5e8..7830991f 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,8 @@ Front Page|Post
- Clean, mobile-friendly interface.
- i18n / internationalization support.
- NSFW post / community support.
+- Cross-posting support.
+- Can transfer site and communities to others.
- High performance.
- Server is written in rust.
- Front end is `~80kB` gzipped.
@@ -161,7 +163,7 @@ Lemmy is free, open-source software, meaning no advertising, monetizing, or vent
If you'd like to add translations, take a look a look at the [english translation file](ui/src/translations/en.ts).
-- Languages supported: English (`en`), Chinese (`zh`), French (`fr`), Swedish (`sv`), German (`de`), Russian (`ru`).
+- Languages supported: English (`en`), Chinese (`zh`), French (`fr`), Spanish (`es`), Swedish (`sv`), German (`de`), Russian (`ru`).
## Credits
diff --git a/server/src/api/community.rs b/server/src/api/community.rs
index 74058488..37bc20db 100644
--- a/server/src/api/community.rs
+++ b/server/src/api/community.rs
@@ -111,6 +111,13 @@ pub struct GetFollowedCommunitiesResponse {
communities: Vec<CommunityFollowerView>
}
+#[derive(Serialize, Deserialize)]
+pub struct TransferCommunity {
+ community_id: i32,
+ user_id: i32,
+ auth: String
+}
+
impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
fn perform(&self) -> Result<GetCommunityResponse, Error> {
let data: &GetCommunity = &self.data;
@@ -148,7 +155,11 @@ impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
}
};
- let admins = UserView::admins(&conn)?;
+ let site_creator_id = Site::read(&conn, 1)?.creator_id;
+ let mut admins = UserView::admins(&conn)?;
+ let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
+ let creator_user = admins.remove(creator_index);
+ admins.insert(0, creator_user);
// Return the jwt
Ok(
@@ -577,3 +588,107 @@ impl Perform<AddModToCommunityResponse> for Oper<AddModToCommunity> {
)
}
}
+
+impl Perform<GetCommunityResponse> for Oper<TransferCommunity> {
+ fn perform(&self) -> Result<GetCommunityResponse, Error> {
+ let data: &TransferCommunity = &self.data;
+ let conn = establish_connection();
+
+ let claims = match Claims::decode(&data.auth) {
+ Ok(claims) => claims.claims,
+ Err(_e) => {
+ return Err(APIError::err(&self.op, "not_logged_in"))?
+ }
+ };
+
+ let user_id = claims.id;
+
+ let read_community = Community::read(&conn, data.community_id)?;
+
+ // Make sure user is the creator
+ if read_community.creator_id != user_id {
+ return Err(APIError::err(&self.op, "not_an_admin"))?
+ }
+
+ let community_form = CommunityForm {
+ name: read_community.name,
+ title: read_community.title,
+ description: read_community.description,
+ category_id: read_community.category_id,
+ creator_id: data.user_id,
+ removed: None,
+ deleted: None,
+ nsfw: read_community.nsfw,
+ updated: Some(naive_now())
+ };
+
+ let _updated_community = match Community::update(&conn, data.community_id, &community_form) {
+ Ok(community) => community,
+ Err(_e) => {
+ return Err(APIError::err(&self.op, "couldnt_update_community"))?
+ }
+ };
+
+ // You also have to re-do the community_moderator table, reordering it.
+ let mut community_mods = CommunityModeratorView::for_community(&conn, data.community_id)?;
+ let creator_index = community_mods.iter().position(|r| r.user_id == data.user_id).unwrap();
+ let creator_user = community_mods.remove(creator_index);
+ community_mods.insert(0, creator_user);
+
+ CommunityModerator::delete_for_community(&conn, data.community_id)?;
+
+ for cmod in &community_mods {
+
+ let community_moderator_form = CommunityModeratorForm {
+ community_id: cmod.community_id,
+ user_id: cmod.user_id
+ };
+
+ let _inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) {
+ Ok(user) => user,
+ Err(_e) => {
+ return Err(APIError::err(&self.op, "community_moderator_already_exists"))?
+ }
+ };
+ }
+
+ // Mod tables
+ let form = ModAddCommunityForm {
+ mod_user_id: user_id,
+ other_user_id: data.user_id,
+ community_id: data.community_id,
+ removed: Some(false),
+ };
+ ModAddCommunity::create(&conn, &form)?;
+
+ let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) {
+ Ok(community) => community,
+ Err(_e) => {
+ return Err(APIError::err(&self.op, "couldnt_find_community"))?
+ }
+ };
+
+ let moderators = match CommunityModeratorView::for_community(&conn, data.community_id) {
+ Ok(moderators) => moderators,
+ Err(_e) => {
+ return Err(APIError::err(&self.op, "couldnt_find_community"))?
+ }
+ };
+
+ let site_creator_id = Site::read(&conn, 1)?.creator_id;
+ let mut admins = UserView::admins(&conn)?;
+ let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
+ let creator_user = admins.remove(creator_index);
+ admins.insert(0, creator_user);
+
+ // Return the jwt
+ Ok(
+ GetCommunityResponse {
+ op: self.op.to_string(),
+ community: community_view,
+ moderators: moderators,
+ admins: admins,
+ }
+ )
+ }
+}
diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs
index 3a4a0865..ac11d30c 100644
--- a/server/src/api/mod.rs
+++ b/server/src/api/mod.rs
@@ -22,7 +22,7 @@ pub mod site;
#[derive(EnumString,ToString,Debug)]
pub enum UserOperation {
- Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead, SaveUserSettings
+ Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead, SaveUserSettings, TransferCommunity, TransferSite
}
#[derive(Fail, Debug)]
diff --git a/server/src/api/post.rs b/server/src/api/post.rs
index c5985f46..8fc24ac1 100644
--- a/server/src/api/post.rs
+++ b/server/src/api/post.rs
@@ -200,7 +200,11 @@ impl Perform<GetPostResponse> for Oper<GetPost> {
let moderators = CommunityModeratorView::for_community(&conn, post_view.community_id)?;
- let admins = UserView::admins(&conn)?;
+ let site_creator_id = Site::read(&conn, 1)?.creator_id;
+ let mut admins = UserView::admins(&conn)?;
+ let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
+ let creator_user = admins.remove(creator_index);
+ admins.insert(0, creator_user);
// Return the jwt
Ok(
diff --git a/server/src/api/site.rs b/server/src/api/site.rs
index c98539be..7827913b 100644
--- a/server/src/api/site.rs
+++ b/server/src/api/site.rs
@@ -83,6 +83,12 @@ pub struct GetSiteResponse {
banned: Vec<UserView>,
}
+#[derive(Serialize, Deserialize)]
+pub struct TransferSite {
+ user_id: i32,
+ auth: String
+}
+
impl Perform<ListCategoriesResponse> for Oper<ListCategories> {
fn perform(&self) -> Result<ListCategoriesResponse, Error> {
let _data: &ListCategories = &self.data;
@@ -251,7 +257,14 @@ impl Perform<GetSiteResponse> for Oper<GetSite> {
Err(_e) => None
};
- let admins = UserView::admins(&conn)?;
+ let mut admins = UserView::admins(&conn)?;
+ if site_view.is_some() {
+ let site_creator_id = site_view.to_owned().unwrap().creator_id;
+ let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
+ let creator_user = admins.remove(creator_index);
+ admins.insert(0, creator_user);
+ }
+
let banned = UserView::banned(&conn)?;
Ok(
@@ -399,3 +412,68 @@ impl Perform<SearchResponse> for Oper<Search> {
)
}
}
+
+impl Perform<GetSiteResponse> for Oper<TransferSite> {
+ fn perform(&self) -> Result<GetSiteResponse, Error> {
+ let data: &TransferSite = &self.data;
+ let conn = establish_connection();
+
+ let claims = match Claims::decode(&data.auth) {
+ Ok(claims) => claims.claims,
+ Err(_e) => {
+ return Err(APIError::err(&self.op, "not_logged_in"))?
+ }
+ };
+
+ let user_id = claims.id;
+
+ let read_site = Site::read(&conn, 1)?;
+
+ // Make sure user is the creator
+ if read_site.creator_id != user_id {
+ return Err(APIError::err(&self.op, "not_an_admin"))?
+ }
+
+ let site_form = SiteForm {
+ name: read_site.name,
+ description: read_site.description,
+ creator_id: data.user_id,
+ updated: Some(naive_now()),
+ };
+
+ match Site::update(&conn, 1, &site_form) {
+ Ok(site) => site,
+ Err(_e) => {
+ return Err(APIError::err(&self.op, "couldnt_update_site"))?
+ }
+ };
+
+ // Mod tables
+ let form = ModAddForm {
+ mod_user_id: user_id,
+ other_user_id: data.user_id,
+ removed: Some(false),
+ };
+
+ ModAdd::create(&conn, &form)?;
+
+ let site_view = SiteView::read(&conn)?;
+
+ let mut admins = UserView::admins(&conn)?;
+ let creator_index = admins.iter().position(|r| r.id == site_view.creator_id).unwrap();
+ let creator_user = admins.remove(creator_index);
+ admins.insert(0, creator_user);
+
+ let banned = UserView::banned(&conn)?;
+
+ Ok(
+ GetSiteResponse {
+ op: self.op.to_string(),
+ site: Some(site_view),
+ admins: admins,
+ banned: banned,
+ }
+ )
+ }
+}
+
diff --git a/server/src/api/user.rs b/server/src/api/user.rs
index 425cc1cb..d8610fa9 100644
--- a/server/src/api/user.rs
+++ b/server/src/api/user.rs
@@ -432,7 +432,11 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
ModAdd::create(&conn, &form)?;
- let admins = UserView::admins(&conn)?;
+ let site_creator_id = Site::read(&conn, 1)?.creator_id;
+ let mut admins = UserView::admins(&conn)?;
+ let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
+ let creator_user = admins.remove(creator_index);
+ admins.insert(0, creator_user);
Ok(
AddAdminResponse {
diff --git a/server/src/db/comment.rs b/server/src/db/comment.rs
index ce0b5a63..7901357f 100644
--- a/server/src/db/comment.rs
+++ b/server/src/db/comment.rs
@@ -103,9 +103,10 @@ impl Likeable <CommentLikeForm> for CommentLike {
}
fn remove(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<usize, Error> {
use crate::schema::comment_like::dsl::*;
- diesel::delete(comment_like
- .filter(comment_id.eq(comment_like_form.comment_id))
- .filter(user_id.eq(comment_like_form.user_id)))
+ diesel::delete(
+ comment_like
+ .filter(comment_id.eq(comment_like_form.comment_id))
+ .filter(user_id.eq(comment_like_form.user_id)))
.execute(conn)
}
}
diff --git a/server/src/db/community.rs b/server/src/db/community.rs
index dd6ea94b..e07b5c00 100644
--- a/server/src/db/community.rs
+++ b/server/src/db/community.rs
@@ -101,6 +101,16 @@ impl Joinable<CommunityModeratorForm> for CommunityModerator {
}
}
+impl CommunityModerator {
+ pub fn delete_for_community(conn: &PgConnection, for_community_id: i32) -> Result<usize, Error> {
+ use crate::schema::community_moderator::dsl::*;
+ diesel::delete(
+ community_moderator
+ .filter(community_id.eq(for_community_id)))
+ .execute(conn)
+ }
+}
+
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
#[belongs_to(Community)]
#[table_name = "community_user_ban"]
diff --git a/server/src/websocket/server.rs b/server/src/websocket/server.rs
index c0dee267..b4cbce3a 100644
--- a/server/src/websocket/server.rs
+++ b/server/src/websocket/server.rs
@@ -490,5 +490,15 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
let res = Oper::new(user_operation, search).perform()?;
Ok(serde_json::to_string(&res)?)
},
+ UserOperation::TransferCommunity => {
+ let transfer_community: TransferCommunity = serde_json::from_str(data)?;
+ let res = Oper::new(user_operation, transfer_community).perform()?;
+ Ok(serde_json::to_string(&res)?)
+ },
+ UserOperation::TransferSite => {
+ let transfer_site: TransferSite = serde_json::from_str(data)?;
+ let res = Oper::new(user_operation, transfer_site).perform()?;
+ Ok(serde_json::to_string(&res)?)
+ },
}
}
diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx
index 610252ea..f518da90 100644
--- a/ui/src/components/comment-node.tsx
+++ b/ui/src/components/comment-node.tsx
@@ -1,6 +1,6 @@
import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router';
-import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI, SaveCommentForm, BanFromCommunityForm, BanUserForm, CommunityUser, UserView, AddModToCommunityForm, AddAdminForm } from '../interfaces';
+import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI, SaveCommentForm, BanFromCommunityForm, BanUserForm, CommunityUser, UserView, AddModToCommunityForm, AddAdminForm, TransferCommunityForm, TransferSiteForm } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { mdToHtml, getUnixTime, canMod, isMod } from '../utils';
import * as moment from 'moment';
@@ -148,6 +148,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}
</>
}
+ {/* Community creators can transfer community to another mod */}
+ {this.amCommunityCreator && this.isMod &&
+ <li className="list-inline-item">
+ <span class="pointer" onClick={linkEvent(this, this.handleTransferCommunity)}><T i18nKey="transfer_community">#</T></span>
+ </li>
+ }
{/* Admins can ban from all, and appoint other admins */}
{this.canAdmin &&
<>
@@ -166,6 +172,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}
</>
}
+ {/* Site Creator can transfer to another admin */}
+ {this.amSiteCreator && this.isAdmin &&
+ <li className="list-inline-item">
+ <span class="pointer" onClick={linkEvent(this, this.handleTransferSite)}><T i18nKey="transfer_site">#</T></span>
+ </li>
+ }
</>
}
<li className="list-inline-item">
@@ -251,6 +263,20 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
return this.props.admins && canMod(UserService.Instance.user, this.props.admins.map(a => a.id), this.props.node.comment.creator_id);
}
+ get amCommunityCreator(): boolean {
+ return this.props.moderators &&
+ UserService.Instance.user &&
+ (this.props.node.comment.creator_id != UserService.Instance.user.id) &&
+ (UserService.Instance.user.id == this.props.moderators[0].user_id);
+ }
+
+ get amSiteCreator(): boolean {
+ return this.props.admins &&
+ UserService.Instance.user &&
+ (this.props.node.comment.creator_id != UserService.Instance.user.id) &&
+ (UserService.Instance.user.id == this.props.admins[0].id);
+ }
+
handleReplyClick(i: CommentNode) {
i.state.showReply = true;
i.setState(i.state);
@@ -431,6 +457,23 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
i.setState(i.state);
}
+ handleTransferCommunity(i: CommentNode) {
+ let form: TransferCommunityForm = {
+ community_id: i.props.node.comment.community_id,
+ user_id: i.props.node.comment.creator_id,
+ };
+ WebSocketService.Instance.transferCommunity(form);
+ i.setState(i.state);
+ }
+
+ handleTransferSite(i: CommentNode) {
+ let form: TransferSiteForm = {
+ user_id: i.props.node.comment.creator_id,
+ };
+ WebSocketService.Instance.transferSite(form);
+ i.setState(i.state);
+ }
+
get isCommentNew(): boolean {
let now = moment.utc().subtract(10, 'minutes');
let then = moment.utc(this.props.node.comment.published);
diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx
index ab82ca4f..a6df4105 100644
--- a/ui/src/components/post.tsx
+++ b/ui/src/components/post.tsx
@@ -1,7 +1,7 @@
import { Component, linkEvent } from 'inferno';
import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators';
-import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentSortType, CreatePostLikeResponse, CommunityUser, CommunityResponse, CommentNode as CommentNodeI, BanFromCommunityResponse, BanUserResponse, AddModToCommunityResponse, AddAdminResponse, UserView, SearchType, SortType, SearchForm, SearchResponse } from '../interfaces';
+import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentSortType, CreatePostLikeResponse, CommunityUser, CommunityResponse, CommentNode as CommentNodeI, BanFromCommunityResponse, BanUserResponse, AddModToCommunityResponse, AddAdminResponse, UserView, SearchType, SortType, SearchForm, SearchResponse, GetSiteResponse, GetCommunityResponse } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { msgOp, hotRank } from '../utils';
import { PostListing } from './post-listing';
@@ -370,6 +370,17 @@ export class Post extends Component<any, PostState> {
let res: SearchResponse = msg;
this.state.crossPosts = res.posts.filter(p => p.id != this.state.post.id);
this.setState(this.state);
+ } else if (op == UserOperation.TransferSite) {
+ let res: GetSiteResponse = msg;
+
+ this.state.admins = res.admins;
+ this.setState(this.state);
+ } else if (op == UserOperation.TransferCommunity) {
+ let res: GetCommunityResponse = msg;
+ this.state.community = res.community;
+ this.state.moderators = res.moderators;
+ this.state.admins = res.admins;
+ this.setState(this.state);
}
}
diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts
index 0a3daf66..251b64a0 100644
--- a/ui/src/interfaces.ts
+++ b/ui/src/interfaces.ts
@@ -1,5 +1,5 @@
export enum UserOperation {
- Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead, SaveUserSettings
+ Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser, Search, MarkAllAsRead, SaveUserSettings, TransferCommunity, TransferSite
}
export enum CommentSortType {
@@ -202,6 +202,17 @@ export interface AddModToCommunityForm {
auth?: string;
}
+export interface TransferCommunityForm {
+ community_id: number;
+ user_id: number;
+ auth?: string;
+}
+
+export interface TransferSiteForm {
+ user_id: number;
+ auth?: string;
+}
+
export interface AddModToCommunityResponse {
op: string;
moderators: Array<CommunityUser>;
diff --git a/ui/src/services/WebSocketService.ts b/ui/src/services/WebSocketService.ts
index c34b6b3c..f67dbf6d 100644
--- a/ui/src/services/WebSocketService.ts
+++ b/ui/src/services/WebSocketService.ts
@@ -1,5 +1,5 @@
import { wsUri } from '../env';
-import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, AddAdminForm, BanUserForm, SiteForm, Site, UserView, GetRepliesForm, SearchForm, UserSettingsForm } from '../interfaces';
+import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, TransferCommunityForm, AddAdminForm, TransferSiteForm, BanUserForm, SiteForm, Site, UserView, GetRepliesForm, SearchForm, UserSettingsForm } from '../interfaces';
import { webSocket } from 'rxjs/webSocket';
import { Subject } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
@@ -136,6 +136,16 @@ export class WebSocketService {
this.subject.next(this.wsSendWrapper(UserOperation.AddModToCommunity, form));
}
+ public transferCommunity(form: TransferCommunityForm) {
+ this.setAuth(form);
+ this.subject.next(this.wsSendWrapper(UserOperation.TransferCommunity, form));
+ }
+
+ public transferSite(form: TransferSiteForm) {
+ this.setAuth(form);
+ this.subject.next(this.wsSendWrapper(UserOperation.TransferSite, form));
+ }
+
public banUser(form: BanUserForm) {
this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.BanUser, form));
diff --git a/ui/src/translations/en.ts b/ui/src/translations/en.ts
index ff38a84b..c854ea7b 100644
--- a/ui/src/translations/en.ts
+++ b/ui/src/translations/en.ts
@@ -130,6 +130,8 @@ export const en = {
joined: 'Joined',
by: 'by',
to: 'to',
+ transfer_community: 'transfer community',
+ transfer_site: 'transfer site',
powered_by: 'Powered by',
landing_0: 'Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>It\'s self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.',
not_logged_in: 'Not logged in.',