Merge branch 'transfer_community' into dev
This commit is contained in:
commit
37ab2bab2e
15 changed files with 312 additions and 15 deletions
3
.travis.yml
vendored
3
.travis.yml
vendored
|
@ -12,9 +12,6 @@ before_script:
|
||||||
before_install:
|
before_install:
|
||||||
- cd server
|
- cd server
|
||||||
script:
|
script:
|
||||||
- cargo install --force diesel_cli --no-default-features --features postgres
|
|
||||||
- diesel migration run
|
|
||||||
- cargo build --all
|
|
||||||
- cargo test --all
|
- cargo test --all
|
||||||
env:
|
env:
|
||||||
- DATABASE_URL=postgres://rrr:rrr@localhost/rrr
|
- DATABASE_URL=postgres://rrr:rrr@localhost/rrr
|
||||||
|
|
1
README.md
vendored
1
README.md
vendored
|
@ -40,6 +40,7 @@ Front Page|Post
|
||||||
- i18n / internationalization support.
|
- i18n / internationalization support.
|
||||||
- NSFW post / community support.
|
- NSFW post / community support.
|
||||||
- Cross-posting support.
|
- Cross-posting support.
|
||||||
|
- Can transfer site and communities to others.
|
||||||
- High performance.
|
- High performance.
|
||||||
- Server is written in rust.
|
- Server is written in rust.
|
||||||
- Front end is `~80kB` gzipped.
|
- Front end is `~80kB` gzipped.
|
||||||
|
|
|
@ -111,6 +111,13 @@ pub struct GetFollowedCommunitiesResponse {
|
||||||
communities: Vec<CommunityFollowerView>
|
communities: Vec<CommunityFollowerView>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct TransferCommunity {
|
||||||
|
community_id: i32,
|
||||||
|
user_id: i32,
|
||||||
|
auth: String
|
||||||
|
}
|
||||||
|
|
||||||
impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
|
impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
|
||||||
fn perform(&self) -> Result<GetCommunityResponse, Error> {
|
fn perform(&self) -> Result<GetCommunityResponse, Error> {
|
||||||
let data: &GetCommunity = &self.data;
|
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
|
// Return the jwt
|
||||||
Ok(
|
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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub mod site;
|
||||||
|
|
||||||
#[derive(EnumString,ToString,Debug)]
|
#[derive(EnumString,ToString,Debug)]
|
||||||
pub enum UserOperation {
|
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)]
|
#[derive(Fail, Debug)]
|
||||||
|
|
|
@ -200,7 +200,11 @@ impl Perform<GetPostResponse> for Oper<GetPost> {
|
||||||
|
|
||||||
let moderators = CommunityModeratorView::for_community(&conn, post_view.community_id)?;
|
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
|
// Return the jwt
|
||||||
Ok(
|
Ok(
|
||||||
|
|
|
@ -83,6 +83,12 @@ pub struct GetSiteResponse {
|
||||||
banned: Vec<UserView>,
|
banned: Vec<UserView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct TransferSite {
|
||||||
|
user_id: i32,
|
||||||
|
auth: String
|
||||||
|
}
|
||||||
|
|
||||||
impl Perform<ListCategoriesResponse> for Oper<ListCategories> {
|
impl Perform<ListCategoriesResponse> for Oper<ListCategories> {
|
||||||
fn perform(&self) -> Result<ListCategoriesResponse, Error> {
|
fn perform(&self) -> Result<ListCategoriesResponse, Error> {
|
||||||
let _data: &ListCategories = &self.data;
|
let _data: &ListCategories = &self.data;
|
||||||
|
@ -251,7 +257,14 @@ impl Perform<GetSiteResponse> for Oper<GetSite> {
|
||||||
Err(_e) => None
|
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)?;
|
let banned = UserView::banned(&conn)?;
|
||||||
|
|
||||||
Ok(
|
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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -432,7 +432,11 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
|
||||||
|
|
||||||
ModAdd::create(&conn, &form)?;
|
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(
|
Ok(
|
||||||
AddAdminResponse {
|
AddAdminResponse {
|
||||||
|
|
|
@ -103,9 +103,10 @@ impl Likeable <CommentLikeForm> for CommentLike {
|
||||||
}
|
}
|
||||||
fn remove(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<usize, Error> {
|
fn remove(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<usize, Error> {
|
||||||
use crate::schema::comment_like::dsl::*;
|
use crate::schema::comment_like::dsl::*;
|
||||||
diesel::delete(comment_like
|
diesel::delete(
|
||||||
.filter(comment_id.eq(comment_like_form.comment_id))
|
comment_like
|
||||||
.filter(user_id.eq(comment_like_form.user_id)))
|
.filter(comment_id.eq(comment_like_form.comment_id))
|
||||||
|
.filter(user_id.eq(comment_like_form.user_id)))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||||
#[belongs_to(Community)]
|
#[belongs_to(Community)]
|
||||||
#[table_name = "community_user_ban"]
|
#[table_name = "community_user_ban"]
|
||||||
|
|
|
@ -490,5 +490,15 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
|
||||||
let res = Oper::new(user_operation, search).perform()?;
|
let res = Oper::new(user_operation, search).perform()?;
|
||||||
Ok(serde_json::to_string(&res)?)
|
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)?)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
45
ui/src/components/comment-node.tsx
vendored
45
ui/src/components/comment-node.tsx
vendored
|
@ -1,6 +1,6 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Link } from 'inferno-router';
|
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 { WebSocketService, UserService } from '../services';
|
||||||
import { mdToHtml, getUnixTime, canMod, isMod } from '../utils';
|
import { mdToHtml, getUnixTime, canMod, isMod } from '../utils';
|
||||||
import * as moment from 'moment';
|
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 */}
|
{/* Admins can ban from all, and appoint other admins */}
|
||||||
{this.canAdmin &&
|
{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">
|
<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);
|
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) {
|
handleReplyClick(i: CommentNode) {
|
||||||
i.state.showReply = true;
|
i.state.showReply = true;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
|
@ -431,6 +457,23 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
i.setState(i.state);
|
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 {
|
get isCommentNew(): boolean {
|
||||||
let now = moment.utc().subtract(10, 'minutes');
|
let now = moment.utc().subtract(10, 'minutes');
|
||||||
let then = moment.utc(this.props.node.comment.published);
|
let then = moment.utc(this.props.node.comment.published);
|
||||||
|
|
13
ui/src/components/post.tsx
vendored
13
ui/src/components/post.tsx
vendored
|
@ -1,7 +1,7 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Subscription } from "rxjs";
|
import { Subscription } from "rxjs";
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
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 { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp, hotRank } from '../utils';
|
import { msgOp, hotRank } from '../utils';
|
||||||
import { PostListing } from './post-listing';
|
import { PostListing } from './post-listing';
|
||||||
|
@ -370,6 +370,17 @@ export class Post extends Component<any, PostState> {
|
||||||
let res: SearchResponse = msg;
|
let res: SearchResponse = msg;
|
||||||
this.state.crossPosts = res.posts.filter(p => p.id != this.state.post.id);
|
this.state.crossPosts = res.posts.filter(p => p.id != this.state.post.id);
|
||||||
this.setState(this.state);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
13
ui/src/interfaces.ts
vendored
13
ui/src/interfaces.ts
vendored
|
@ -1,5 +1,5 @@
|
||||||
export enum UserOperation {
|
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 {
|
export enum CommentSortType {
|
||||||
|
@ -202,6 +202,17 @@ export interface AddModToCommunityForm {
|
||||||
auth?: string;
|
auth?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TransferCommunityForm {
|
||||||
|
community_id: number;
|
||||||
|
user_id: number;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransferSiteForm {
|
||||||
|
user_id: number;
|
||||||
|
auth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AddModToCommunityResponse {
|
export interface AddModToCommunityResponse {
|
||||||
op: string;
|
op: string;
|
||||||
moderators: Array<CommunityUser>;
|
moderators: Array<CommunityUser>;
|
||||||
|
|
12
ui/src/services/WebSocketService.ts
vendored
12
ui/src/services/WebSocketService.ts
vendored
|
@ -1,5 +1,5 @@
|
||||||
import { wsUri } from '../env';
|
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 { webSocket } from 'rxjs/webSocket';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
|
@ -136,6 +136,16 @@ export class WebSocketService {
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.AddModToCommunity, form));
|
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) {
|
public banUser(form: BanUserForm) {
|
||||||
this.setAuth(form);
|
this.setAuth(form);
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.BanUser, form));
|
this.subject.next(this.wsSendWrapper(UserOperation.BanUser, form));
|
||||||
|
|
2
ui/src/translations/en.ts
vendored
2
ui/src/translations/en.ts
vendored
|
@ -130,6 +130,8 @@ export const en = {
|
||||||
joined: 'Joined',
|
joined: 'Joined',
|
||||||
by: 'by',
|
by: 'by',
|
||||||
to: 'to',
|
to: 'to',
|
||||||
|
transfer_community: 'transfer community',
|
||||||
|
transfer_site: 'transfer site',
|
||||||
powered_by: 'Powered by',
|
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>.',
|
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.',
|
not_logged_in: 'Not logged in.',
|
||||||
|
|
Reference in a new issue