Adding some site oriented settings.

- Adding option to close registration. Fixes #350
- Adding option to disable showing NSFW buttons. Fixes #364
- Adding option to disable downvotes. Fixes #239
This commit is contained in:
Dessalines 2019-12-11 12:21:47 -08:00
parent 688378aa5a
commit aee6ee5a7c
25 changed files with 349 additions and 167 deletions

View File

@ -0,0 +1,16 @@
-- Drop the columns
drop view site_view;
alter table site drop column enable_downvotes;
alter table site drop column open_registration;
alter table site drop column enable_nsfw;
-- Rebuild the views
create view site_view as
select *,
(select name from user_ u where s.creator_id = u.id) as creator_name,
(select count(*) from user_) as number_of_users,
(select count(*) from post) as number_of_posts,
(select count(*) from comment) as number_of_comments,
(select count(*) from community) as number_of_communities
from site s;

View File

@ -0,0 +1,16 @@
-- Add the column
alter table site add column enable_downvotes boolean default true not null;
alter table site add column open_registration boolean default true not null;
alter table site add column enable_nsfw boolean default true not null;
-- Reload the view
drop view site_view;
create view site_view as
select *,
(select name from user_ u where s.creator_id = u.id) as creator_name,
(select count(*) from user_) as number_of_users,
(select count(*) from post) as number_of_posts,
(select count(*) from comment) as number_of_comments,
(select count(*) from community) as number_of_communities
from site s;

View File

@ -298,6 +298,14 @@ impl Perform<CommentResponse> for Oper<CreateCommentLike> {
let user_id = claims.id; let user_id = claims.id;
// Don't do a downvote if site has downvotes disabled
if data.score == -1 {
let site = SiteView::read(&conn)?;
if site.enable_downvotes == false {
return Err(APIError::err(&self.op, "downvotes_disabled"))?;
}
}
// Check for a community ban // Check for a community ban
let post = Post::read(&conn, data.post_id)?; let post = Post::read(&conn, data.post_id)?;
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {

View File

@ -8,6 +8,8 @@ use crate::db::moderator_views::*;
use crate::db::password_reset_request::*; use crate::db::password_reset_request::*;
use crate::db::post::*; use crate::db::post::*;
use crate::db::post_view::*; use crate::db::post_view::*;
use crate::db::site::*;
use crate::db::site_view::*;
use crate::db::user::*; use crate::db::user::*;
use crate::db::user_mention::*; use crate::db::user_mention::*;
use crate::db::user_mention_view::*; use crate::db::user_mention_view::*;

View File

@ -265,6 +265,14 @@ impl Perform<CreatePostLikeResponse> for Oper<CreatePostLike> {
let user_id = claims.id; let user_id = claims.id;
// Don't do a downvote if site has downvotes disabled
if data.score == -1 {
let site = SiteView::read(&conn)?;
if site.enable_downvotes == false {
return Err(APIError::err(&self.op, "downvotes_disabled"))?;
}
}
// Check for a community ban // Check for a community ban
let post = Post::read(&conn, data.post_id)?; let post = Post::read(&conn, data.post_id)?;
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {

View File

@ -56,6 +56,9 @@ pub struct GetModlogResponse {
pub struct CreateSite { pub struct CreateSite {
name: String, name: String,
description: Option<String>, description: Option<String>,
enable_downvotes: bool,
open_registration: bool,
enable_nsfw: bool,
auth: String, auth: String,
} }
@ -63,6 +66,9 @@ pub struct CreateSite {
pub struct EditSite { pub struct EditSite {
name: String, name: String,
description: Option<String>, description: Option<String>,
enable_downvotes: bool,
open_registration: bool,
enable_nsfw: bool,
auth: String, auth: String,
} }
@ -208,6 +214,9 @@ impl Perform<SiteResponse> for Oper<CreateSite> {
name: data.name.to_owned(), name: data.name.to_owned(),
description: data.description.to_owned(), description: data.description.to_owned(),
creator_id: user_id, creator_id: user_id,
enable_downvotes: data.enable_downvotes,
open_registration: data.open_registration,
enable_nsfw: data.enable_nsfw,
updated: None, updated: None,
}; };
@ -255,6 +264,9 @@ impl Perform<SiteResponse> for Oper<EditSite> {
description: data.description.to_owned(), description: data.description.to_owned(),
creator_id: found_site.creator_id, creator_id: found_site.creator_id,
updated: Some(naive_now()), updated: Some(naive_now()),
enable_downvotes: data.enable_downvotes,
open_registration: data.open_registration,
enable_nsfw: data.enable_nsfw,
}; };
match Site::update(&conn, 1, &site_form) { match Site::update(&conn, 1, &site_form) {
@ -431,6 +443,9 @@ impl Perform<GetSiteResponse> for Oper<TransferSite> {
description: read_site.description, description: read_site.description,
creator_id: data.user_id, creator_id: data.user_id,
updated: Some(naive_now()), updated: Some(naive_now()),
enable_downvotes: read_site.enable_downvotes,
open_registration: read_site.open_registration,
enable_nsfw: read_site.enable_nsfw,
}; };
match Site::update(&conn, 1, &site_form) { match Site::update(&conn, 1, &site_form) {

View File

@ -193,6 +193,13 @@ impl Perform<LoginResponse> for Oper<Register> {
let data: &Register = &self.data; let data: &Register = &self.data;
let conn = establish_connection(); let conn = establish_connection();
// Make sure site has open registration
if let Ok(site) = SiteView::read(&conn) {
if !site.open_registration {
return Err(APIError::err(&self.op, "registration_closed"))?;
}
}
// Make sure passwords match // Make sure passwords match
if &data.password != &data.password_verify { if &data.password != &data.password_verify {
return Err(APIError::err(&self.op, "passwords_dont_match"))?; return Err(APIError::err(&self.op, "passwords_dont_match"))?;

View File

@ -1,5 +1,5 @@
use super::*; use super::*;
use crate::schema::{community, community_follower, community_moderator, community_user_ban, site}; use crate::schema::{community, community_follower, community_moderator, community_user_ban};
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "community"] #[table_name = "community"]
@ -202,50 +202,6 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
} }
} }
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "site"]
pub struct Site {
pub id: i32,
pub name: String,
pub description: Option<String>,
pub creator_id: i32,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
#[table_name = "site"]
pub struct SiteForm {
pub name: String,
pub description: Option<String>,
pub creator_id: i32,
pub updated: Option<chrono::NaiveDateTime>,
}
impl Crud<SiteForm> for Site {
fn read(conn: &PgConnection, _site_id: i32) -> Result<Self, Error> {
use crate::schema::site::dsl::*;
site.first::<Self>(conn)
}
fn delete(conn: &PgConnection, site_id: i32) -> Result<usize, Error> {
use crate::schema::site::dsl::*;
diesel::delete(site.find(site_id)).execute(conn)
}
fn create(conn: &PgConnection, new_site: &SiteForm) -> Result<Self, Error> {
use crate::schema::site::dsl::*;
insert_into(site).values(new_site).get_result::<Self>(conn)
}
fn update(conn: &PgConnection, site_id: i32, new_site: &SiteForm) -> Result<Self, Error> {
use crate::schema::site::dsl::*;
diesel::update(site.find(site_id))
.set(new_site)
.get_result::<Self>(conn)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::user::*; use super::super::user::*;

View File

@ -59,22 +59,6 @@ table! {
} }
} }
table! {
site_view (id) {
id -> Int4,
name -> Varchar,
description -> Nullable<Text>,
creator_id -> Int4,
published -> Timestamp,
updated -> Nullable<Timestamp>,
creator_name -> Varchar,
number_of_users -> BigInt,
number_of_posts -> BigInt,
number_of_comments -> BigInt,
number_of_communities -> BigInt,
}
}
#[derive( #[derive(
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone, Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
)] )]
@ -328,28 +312,3 @@ impl CommunityUserBanView {
.first::<Self>(conn) .first::<Self>(conn)
} }
} }
#[derive(
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
)]
#[table_name = "site_view"]
pub struct SiteView {
pub id: i32,
pub name: String,
pub description: Option<String>,
pub creator_id: i32,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub creator_name: String,
pub number_of_users: i64,
pub number_of_posts: i64,
pub number_of_comments: i64,
pub number_of_communities: i64,
}
impl SiteView {
pub fn read(conn: &PgConnection) -> Result<Self, Error> {
use super::community_view::site_view::dsl::*;
site_view.first::<Self>(conn)
}
}

View File

@ -14,6 +14,8 @@ pub mod moderator_views;
pub mod password_reset_request; pub mod password_reset_request;
pub mod post; pub mod post;
pub mod post_view; pub mod post_view;
pub mod site;
pub mod site_view;
pub mod user; pub mod user;
pub mod user_mention; pub mod user_mention;
pub mod user_mention_view; pub mod user_mention_view;

52
server/src/db/site.rs Normal file
View File

@ -0,0 +1,52 @@
use super::*;
use crate::schema::site;
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "site"]
pub struct Site {
pub id: i32,
pub name: String,
pub description: Option<String>,
pub creator_id: i32,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub enable_downvotes: bool,
pub open_registration: bool,
pub enable_nsfw: bool,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
#[table_name = "site"]
pub struct SiteForm {
pub name: String,
pub description: Option<String>,
pub creator_id: i32,
pub updated: Option<chrono::NaiveDateTime>,
pub enable_downvotes: bool,
pub open_registration: bool,
pub enable_nsfw: bool,
}
impl Crud<SiteForm> for Site {
fn read(conn: &PgConnection, _site_id: i32) -> Result<Self, Error> {
use crate::schema::site::dsl::*;
site.first::<Self>(conn)
}
fn delete(conn: &PgConnection, site_id: i32) -> Result<usize, Error> {
use crate::schema::site::dsl::*;
diesel::delete(site.find(site_id)).execute(conn)
}
fn create(conn: &PgConnection, new_site: &SiteForm) -> Result<Self, Error> {
use crate::schema::site::dsl::*;
insert_into(site).values(new_site).get_result::<Self>(conn)
}
fn update(conn: &PgConnection, site_id: i32, new_site: &SiteForm) -> Result<Self, Error> {
use crate::schema::site::dsl::*;
diesel::update(site.find(site_id))
.set(new_site)
.get_result::<Self>(conn)
}
}

View File

@ -0,0 +1,48 @@
use super::*;
table! {
site_view (id) {
id -> Int4,
name -> Varchar,
description -> Nullable<Text>,
creator_id -> Int4,
published -> Timestamp,
updated -> Nullable<Timestamp>,
enable_downvotes -> Bool,
open_registration -> Bool,
enable_nsfw -> Bool,
creator_name -> Varchar,
number_of_users -> BigInt,
number_of_posts -> BigInt,
number_of_comments -> BigInt,
number_of_communities -> BigInt,
}
}
#[derive(
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
)]
#[table_name = "site_view"]
pub struct SiteView {
pub id: i32,
pub name: String,
pub description: Option<String>,
pub creator_id: i32,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub enable_downvotes: bool,
pub open_registration: bool,
pub enable_nsfw: bool,
pub creator_name: String,
pub number_of_users: i64,
pub number_of_posts: i64,
pub number_of_comments: i64,
pub number_of_communities: i64,
}
impl SiteView {
pub fn read(conn: &PgConnection) -> Result<Self, Error> {
use super::site_view::site_view::dsl::*;
site_view.first::<Self>(conn)
}
}

View File

@ -3,8 +3,8 @@ extern crate rss;
use super::*; use super::*;
use crate::db::comment_view::{ReplyQueryBuilder, ReplyView}; use crate::db::comment_view::{ReplyQueryBuilder, ReplyView};
use crate::db::community::Community; use crate::db::community::Community;
use crate::db::community_view::SiteView;
use crate::db::post_view::{PostQueryBuilder, PostView}; use crate::db::post_view::{PostQueryBuilder, PostView};
use crate::db::site_view::SiteView;
use crate::db::user::User_; use crate::db::user::User_;
use crate::db::user_mention_view::{UserMentionQueryBuilder, UserMentionView}; use crate::db::user_mention_view::{UserMentionQueryBuilder, UserMentionView};
use crate::db::{establish_connection, ListingType, SortType}; use crate::db::{establish_connection, ListingType, SortType};

View File

@ -118,7 +118,7 @@ impl Settings {
.unwrap_or("3600".to_string()) .unwrap_or("3600".to_string())
.parse() .parse()
.unwrap(), .unwrap(),
email_config: email_config, email_config,
} }
} }
fn api_endpoint(&self) -> String { fn api_endpoint(&self) -> String {

View File

@ -1,5 +1,5 @@
use crate::db::community_view::SiteView;
use crate::db::establish_connection; use crate::db::establish_connection;
use crate::db::site_view::SiteView;
use crate::version; use crate::version;
use crate::Settings; use crate::Settings;
use actix_web::body::Body; use actix_web::body::Body;

View File

@ -246,6 +246,9 @@ table! {
creator_id -> Int4, creator_id -> Int4,
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
enable_downvotes -> Bool,
open_registration -> Bool,
enable_nsfw -> Bool,
} }
} }

View File

@ -102,16 +102,18 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<div class={`font-weight-bold text-muted`}> <div class={`font-weight-bold text-muted`}>
{node.comment.score} {node.comment.score}
</div> </div>
<button {WebSocketService.Instance.site.enable_downvotes && (
className={`btn p-0 ${ <button
node.comment.my_vote == -1 ? 'text-danger' : 'text-muted' className={`btn p-0 ${
}`} node.comment.my_vote == -1 ? 'text-danger' : 'text-muted'
onClick={linkEvent(node, this.handleCommentDisLike)} }`}
> onClick={linkEvent(node, this.handleCommentDisLike)}
<svg class="icon downvote"> >
<use xlinkHref="#icon-arrow-down"></use> <svg class="icon downvote">
</svg> <use xlinkHref="#icon-arrow-down"></use>
</button> </svg>
</button>
)}
</div> </div>
)} )}
<div <div

View File

@ -156,21 +156,24 @@ export class CommunityForm extends Component<
</select> </select>
</div> </div>
</div> </div>
<div class="form-group row">
<div class="col-12"> {WebSocketService.Instance.site.enable_nsfw && (
<div class="form-check"> <div class="form-group row">
<input <div class="col-12">
class="form-check-input" <div class="form-check">
type="checkbox" <input
checked={this.state.communityForm.nsfw} class="form-check-input"
onChange={linkEvent(this, this.handleCommunityNsfwChange)} type="checkbox"
/> checked={this.state.communityForm.nsfw}
<label class="form-check-label"> onChange={linkEvent(this, this.handleCommunityNsfwChange)}
<T i18nKey="nsfw">#</T> />
</label> <label class="form-check-label">
<T i18nKey="nsfw">#</T>
</label>
</div>
</div> </div>
</div> </div>
</div> )}
<div class="form-group row"> <div class="form-group row">
<div class="col-12"> <div class="col-12">
<button type="submit" class="btn btn-secondary mr-2"> <button type="submit" class="btn btn-secondary mr-2">

View File

@ -205,21 +205,23 @@ export class Login extends Component<any, State> {
/> />
</div> </div>
</div> </div>
<div class="form-group row"> {WebSocketService.Instance.site.enable_nsfw && (
<div class="col-sm-10"> <div class="form-group row">
<div class="form-check"> <div class="col-sm-10">
<input <div class="form-check">
class="form-check-input" <input
type="checkbox" class="form-check-input"
checked={this.state.registerForm.show_nsfw} type="checkbox"
onChange={linkEvent(this, this.handleRegisterShowNsfwChange)} checked={this.state.registerForm.show_nsfw}
/> onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
<label class="form-check-label"> />
<T i18nKey="show_nsfw">#</T> <label class="form-check-label">
</label> <T i18nKey="show_nsfw">#</T>
</label>
</div>
</div> </div>
</div> </div>
</div> )}
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-10"> <div class="col-sm-10">
<button type="submit" class="btn btn-secondary"> <button type="submit" class="btn btn-secondary">

View File

@ -280,21 +280,23 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
</div> </div>
</div> </div>
)} )}
<div class="form-group row"> {WebSocketService.Instance.site.enable_nsfw && (
<div class="col-sm-10"> <div class="form-group row">
<div class="form-check"> <div class="col-sm-10">
<input <div class="form-check">
class="form-check-input" <input
type="checkbox" class="form-check-input"
checked={this.state.postForm.nsfw} type="checkbox"
onChange={linkEvent(this, this.handlePostNsfwChange)} checked={this.state.postForm.nsfw}
/> onChange={linkEvent(this, this.handlePostNsfwChange)}
<label class="form-check-label"> />
<T i18nKey="nsfw">#</T> <label class="form-check-label">
</label> <T i18nKey="nsfw">#</T>
</label>
</div>
</div> </div>
</div> </div>
</div> )}
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-10"> <div class="col-sm-10">
<button type="submit" class="btn btn-secondary mr-2"> <button type="submit" class="btn btn-secondary mr-2">

View File

@ -114,16 +114,18 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</svg> </svg>
</button> </button>
<div class={`font-weight-bold text-muted`}>{post.score}</div> <div class={`font-weight-bold text-muted`}>{post.score}</div>
<button {WebSocketService.Instance.site.enable_downvotes && (
className={`btn p-0 ${ <button
post.my_vote == -1 ? 'text-danger' : 'text-muted' className={`btn p-0 ${
}`} post.my_vote == -1 ? 'text-danger' : 'text-muted'
onClick={linkEvent(this, this.handlePostDisLike)} }`}
> onClick={linkEvent(this, this.handlePostDisLike)}
<svg class="icon downvote"> >
<use xlinkHref="#icon-arrow-down"></use> <svg class="icon downvote">
</svg> <use xlinkHref="#icon-arrow-down"></use>
</button> </svg>
</button>
)}
</div> </div>
{post.url && isImage(post.url) && !post.nsfw && !post.community_nsfw && ( {post.url && isImage(post.url) && !post.nsfw && !post.community_nsfw && (
<span <span

View File

@ -19,6 +19,9 @@ interface SiteFormState {
export class SiteForm extends Component<SiteFormProps, SiteFormState> { export class SiteForm extends Component<SiteFormProps, SiteFormState> {
private emptyState: SiteFormState = { private emptyState: SiteFormState = {
siteForm: { siteForm: {
enable_downvotes: true,
open_registration: true,
enable_nsfw: true,
name: null, name: null,
}, },
loading: false, loading: false,
@ -31,6 +34,9 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
this.state.siteForm = { this.state.siteForm = {
name: this.props.site.name, name: this.props.site.name,
description: this.props.site.description, description: this.props.site.description,
enable_downvotes: this.props.site.enable_downvotes,
open_registration: this.props.site.open_registration,
enable_nsfw: this.props.site.enable_nsfw,
}; };
} }
} }
@ -77,6 +83,54 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
/> />
</div> </div>
</div> </div>
<div class="form-group row">
<div class="col-12">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
checked={this.state.siteForm.enable_downvotes}
onChange={linkEvent(this, this.handleSiteEnableDownvotesChange)}
/>
<label class="form-check-label">
<T i18nKey="enable_downvotes">#</T>
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-12">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
checked={this.state.siteForm.enable_nsfw}
onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
/>
<label class="form-check-label">
<T i18nKey="enable_nsfw">#</T>
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-12">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
checked={this.state.siteForm.open_registration}
onChange={linkEvent(
this,
this.handleSiteOpenRegistrationChange
)}
/>
<label class="form-check-label">
<T i18nKey="open_registration">#</T>
</label>
</div>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<div class="col-12"> <div class="col-12">
<button type="submit" class="btn btn-secondary mr-2"> <button type="submit" class="btn btn-secondary mr-2">
@ -126,6 +180,21 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
i.setState(i.state); i.setState(i.state);
} }
handleSiteEnableNsfwChange(i: SiteForm, event: any) {
i.state.siteForm.enable_nsfw = event.target.checked;
i.setState(i.state);
}
handleSiteOpenRegistrationChange(i: SiteForm, event: any) {
i.state.siteForm.open_registration = event.target.checked;
i.setState(i.state);
}
handleSiteEnableDownvotesChange(i: SiteForm, event: any) {
i.state.siteForm.enable_downvotes = event.target.checked;
i.setState(i.state);
}
handleCancel(i: SiteForm) { handleCancel(i: SiteForm) {
i.props.onCancel(); i.props.onCancel();
} }

View File

@ -496,24 +496,26 @@ export class User extends Component<any, UserState> {
/> />
</div> </div>
</form> </form>
<div class="form-group"> {WebSocketService.Instance.site.enable_nsfw && (
<div class="col-12"> <div class="form-group">
<div class="form-check"> <div class="col-12">
<input <div class="form-check">
class="form-check-input" <input
type="checkbox" class="form-check-input"
checked={this.state.userSettingsForm.show_nsfw} type="checkbox"
onChange={linkEvent( checked={this.state.userSettingsForm.show_nsfw}
this, onChange={linkEvent(
this.handleUserSettingsShowNsfwChange this,
)} this.handleUserSettingsShowNsfwChange
/> )}
<label class="form-check-label"> />
<T i18nKey="show_nsfw">#</T> <label class="form-check-label">
</label> <T i18nKey="show_nsfw">#</T>
</label>
</div>
</div> </div>
</div> </div>
</div> )}
<div class="form-group"> <div class="form-group">
<div class="col-12"> <div class="col-12">
<button <button

View File

@ -199,6 +199,9 @@ export interface Site {
number_of_posts: number; number_of_posts: number;
number_of_comments: number; number_of_comments: number;
number_of_communities: number; number_of_communities: number;
enable_downvotes: boolean;
open_registration: boolean;
enable_nsfw: boolean;
} }
export enum BanType { export enum BanType {
@ -625,9 +628,9 @@ export interface CreatePostLikeResponse {
export interface SiteForm { export interface SiteForm {
name: string; name: string;
description?: string; description?: string;
removed?: boolean; enable_downvotes: boolean;
reason?: string; open_registration: boolean;
expires?: number; enable_nsfw: boolean;
auth?: string; auth?: string;
} }

View File

@ -127,6 +127,11 @@ export const en = {
expires: 'Expires', expires: 'Expires',
language: 'Language', language: 'Language',
browser_default: 'Browser Default', browser_default: 'Browser Default',
downvotes_disabled: 'Downvotes disabled',
enable_downvotes: 'Enable Downvotes',
open_registration: 'Open Registration',
registration_closed: 'Registration closed',
enable_nsfw: 'Enable NSFW',
url: 'URL', url: 'URL',
body: 'Body', body: 'Body',
copy_suggested_title: 'copy suggested title: {{title}}', copy_suggested_title: 'copy suggested title: {{title}}',