Adding new site setup fields.

This commit is contained in:
Dessalines 2022-11-04 20:41:33 -04:00
parent 8fdbaf4499
commit 0c133fe1a6
34 changed files with 1207 additions and 568 deletions

32
dev.dockerfile Normal file
View file

@ -0,0 +1,32 @@
FROM node:alpine as builder
RUN apk update && apk add curl yarn python3 build-base gcc wget git --no-cache
WORKDIR /usr/src/app
# Cache deps
COPY package.json yarn.lock ./
RUN yarn install --ignore-scripts --prefer-offline --pure-lockfile
# Build
COPY generate_translations.js \
tsconfig.json \
webpack.config.js \
.babelrc \
./
COPY lemmy-translations lemmy-translations
COPY src src
# Set UI version
RUN echo "export const VERSION = 'dev';" > "src/shared/version.ts"
RUN yarn install --ignore-scripts --prefer-offline
RUN yarn build:dev
FROM node:alpine as runner
COPY --from=builder /usr/src/app/dist /app/dist
COPY --from=builder /usr/src/app/node_modules /app/node_modules
EXPOSE 1234
WORKDIR /app
CMD node dist/js/server.js

@ -1 +1 @@
Subproject commit 94b9b5debdaa40facf13a852cb096ef0cbd34ad7 Subproject commit 46f4b3e8676c23d4a8100ce78330eceed7bcf053

View file

@ -24,7 +24,6 @@
"@babel/preset-typescript": "^7.18.6", "@babel/preset-typescript": "^7.18.6",
"@babel/runtime": "^7.18.9", "@babel/runtime": "^7.18.9",
"@sniptt/monads": "^0.5.10", "@sniptt/monads": "^0.5.10",
"@typescript-eslint/parser": "^5.31.0",
"autosize": "^5.0.1", "autosize": "^5.0.1",
"babel-loader": "^8.2.5", "babel-loader": "^8.2.5",
"babel-plugin-inferno": "^6.5.0", "babel-plugin-inferno": "^6.5.0",
@ -48,7 +47,7 @@
"inferno-server": "^8.0.3", "inferno-server": "^8.0.3",
"isomorphic-cookie": "^1.2.4", "isomorphic-cookie": "^1.2.4",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lemmy-js-client": "0.17.0-rc.46", "lemmy-js-client": "0.17.0-rc.51",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"markdown-it-container": "^3.0.0", "markdown-it-container": "^3.0.0",
"markdown-it-footnote": "^3.0.3", "markdown-it-footnote": "^3.0.3",
@ -82,6 +81,7 @@
"@types/node-fetch": "^2.6.2", "@types/node-fetch": "^2.6.2",
"@types/serialize-javascript": "^5.0.1", "@types/serialize-javascript": "^5.0.1",
"@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/eslint-plugin": "^5.31.0",
"@typescript-eslint/parser": "^5.31.0",
"bootstrap": "^5.2.0", "bootstrap": "^5.2.0",
"bootswatch": "^5.2.0", "bootswatch": "^5.2.0",
"eslint": "^8.20.0", "eslint": "^8.20.0",

View file

@ -24,23 +24,21 @@ export class App extends Component<any, any> {
<> <>
<Provider i18next={i18n}> <Provider i18next={i18n}>
<div> <div>
<Theme defaultTheme={siteView.map(s => s.site.default_theme)} /> <Theme defaultTheme={siteView.local_site.default_theme} />
{siteView {siteView.site.icon.match({
.andThen(s => s.site.icon) some: icon => (
.match({ <Helmet>
some: icon => ( <link
<Helmet> id="favicon"
<link rel="shortcut icon"
id="favicon" type="image/x-icon"
rel="shortcut icon" href={icon || favIconUrl}
type="image/x-icon" />
href={icon || favIconUrl} <link rel="apple-touch-icon" href={icon || favIconPngUrl} />
/> </Helmet>
<link rel="apple-touch-icon" href={icon || favIconPngUrl} /> ),
</Helmet> none: <></>,
), })}
none: <></>,
})}
<Navbar siteRes={siteRes} /> <Navbar siteRes={siteRes} />
<div className="mt-4 p-0 fl-1"> <div className="mt-4 p-0 fl-1">
<Switch> <Switch>

View file

@ -32,9 +32,7 @@ export class Footer extends Component<FooterProps, any> {
{i18n.t("modlog")} {i18n.t("modlog")}
</NavLink> </NavLink>
</li> </li>
{this.props.site.site_view {this.props.site.site_view.local_site.legal_information.isSome() && (
.andThen(s => s.site.legal_information)
.isSome() && (
<li className="nav-item"> <li className="nav-item">
<NavLink className="nav-link" to="/legal"> <NavLink className="nav-link" to="/legal">
{i18n.t("legal_information")} {i18n.t("legal_information")}

View file

@ -90,9 +90,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
}) })
); );
if (this.props.siteRes.site_view.isSome()) { this.fetchUnreads();
this.fetchUnreads();
}
} }
this.requestNotificationPermission(); this.requestNotificationPermission();
@ -144,27 +142,22 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
// TODO class active corresponding to current page // TODO class active corresponding to current page
navbar() { navbar() {
let siteView = this.props.siteRes.site_view;
return ( return (
<nav className="navbar navbar-expand-md navbar-light shadow-sm p-0 px-3"> <nav className="navbar navbar-expand-md navbar-light shadow-sm p-0 px-3">
<div className="container-lg"> <div className="container-lg">
{this.props.siteRes.site_view.match({ <NavLink
some: siteView => ( to="/"
<NavLink onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
to="/" title={siteView.site.description.unwrapOr(siteView.site.name)}
onMouseUp={linkEvent(this, this.handleHideExpandNavbar)} className="d-flex align-items-center navbar-brand mr-md-3"
title={siteView.site.description.unwrapOr(siteView.site.name)} >
className="d-flex align-items-center navbar-brand mr-md-3" {siteView.site.icon.match({
> some: icon => showAvatars() && <PictrsImage src={icon} icon />,
{siteView.site.icon.match({ none: <></>,
some: icon => })}
showAvatars() && <PictrsImage src={icon} icon />, {siteView.site.name}
none: <></>, </NavLink>
})}
{siteView.site.name}
</NavLink>
),
none: <></>,
})}
{UserService.Instance.myUserInfo.isSome() && ( {UserService.Instance.myUserInfo.isSome() && (
<> <>
<ul className="navbar-nav ml-auto"> <ul className="navbar-nav ml-auto">

View file

@ -1,10 +1,9 @@
import { Option } from "@sniptt/monads";
import { Component } from "inferno"; import { Component } from "inferno";
import { Helmet } from "inferno-helmet"; import { Helmet } from "inferno-helmet";
import { UserService } from "../../services"; import { UserService } from "../../services";
interface Props { interface Props {
defaultTheme: Option<string>; defaultTheme: string;
} }
export class Theme extends Component<Props> { export class Theme extends Component<Props> {
@ -26,16 +25,13 @@ export class Theme extends Component<Props> {
/> />
</Helmet> </Helmet>
); );
} else if ( } else if (this.props.defaultTheme != "browser") {
this.props.defaultTheme.isSome() &&
this.props.defaultTheme.unwrap() != "browser"
) {
return ( return (
<Helmet> <Helmet>
<link <link
rel="stylesheet" rel="stylesheet"
type="text/css" type="text/css"
href={`/css/themes/${this.props.defaultTheme.unwrap()}.css`} href={`/css/themes/${this.props.defaultTheme}.css`}
/> />
</Helmet> </Helmet>
); );

View file

@ -109,10 +109,9 @@ export class Communities extends Component<any, CommunitiesState> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return `${i18n.t("communities")} - ${
some: siteView => `${i18n.t("communities")} - ${siteView.site.name}`, this.state.siteRes.site_view.site.name
none: "", }`;
});
} }
render() { render() {

View file

@ -272,11 +272,7 @@ export class Community extends Component<any, State> {
get documentTitle(): string { get documentTitle(): string {
return this.state.communityRes.match({ return this.state.communityRes.match({
some: res => some: res =>
this.state.siteRes.site_view.match({ `${res.community_view.community.title} - ${this.state.siteRes.site_view.site.name}`,
some: siteView =>
`${res.community_view.community.title} - ${siteView.site.name}`,
none: "",
}),
none: "", none: "",
}); });
} }

View file

@ -48,10 +48,9 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return `${i18n.t("create_community")} - ${
some: siteView => `${i18n.t("create_community")} - ${siteView.site.name}`, this.state.siteRes.site_view.site.name
none: "", }`;
});
} }
render() { render() {

View file

@ -1,4 +1,4 @@
import { None, Some } from "@sniptt/monads"; import { None } from "@sniptt/monads";
import autosize from "autosize"; import autosize from "autosize";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { import {
@ -96,10 +96,9 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return `${i18n.t("admin_settings")} - ${
some: siteView => `${i18n.t("admin_settings")} - ${siteView.site.name}`, this.state.siteRes.site_view.site.name
none: "", }`;
});
} }
render() { render() {
@ -118,15 +117,10 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
description={None} description={None}
image={None} image={None}
/> />
{this.state.siteRes.site_view.match({ <SiteForm
some: siteView => ( siteRes={this.state.siteRes}
<SiteForm showLocal={showLocal(this.isoData)}
site={Some(siteView.site)} />
showLocal={showLocal(this.isoData)}
/>
),
none: <></>,
})}
</div> </div>
<div className="col-12 col-md-6"> <div className="col-12 col-md-6">
{this.admins()} {this.admins()}
@ -201,7 +195,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
return; return;
} else if (op == UserOperation.EditSite) { } else if (op == UserOperation.EditSite) {
let data = wsJsonToRes<SiteResponse>(msg, SiteResponse); let data = wsJsonToRes<SiteResponse>(msg, SiteResponse);
this.setState(s => ((s.siteRes.site_view = Some(data.site_view)), s)); this.setState(s => ((s.siteRes.site_view = data.site_view), s));
toast(i18n.t("site_saved")); toast(i18n.t("site_saved"));
} else if (op == UserOperation.GetBannedPersons) { } else if (op == UserOperation.GetBannedPersons) {
let data = wsJsonToRes<BannedPersonsResponse>(msg, BannedPersonsResponse); let data = wsJsonToRes<BannedPersonsResponse>(msg, BannedPersonsResponse);

View file

@ -131,10 +131,7 @@ export class Home extends Component<any, HomeState> {
listingType: getListingTypeFromProps( listingType: getListingTypeFromProps(
this.props, this.props,
ListingType[ ListingType[
this.isoData.site_res.site_view.match({ this.isoData.site_res.site_view.local_site.default_post_listing_type
some: type_ => type_.site.default_post_listing_type,
none: ListingType.Local,
})
] ]
), ),
dataType: getDataTypeFromProps(this.props), dataType: getDataTypeFromProps(this.props),
@ -199,7 +196,7 @@ export class Home extends Component<any, HomeState> {
componentDidMount() { componentDidMount() {
// This means it hasn't been set up yet // This means it hasn't been set up yet
if (this.state.siteRes.site_view.isNone()) { if (!this.state.siteRes.site_view.local_site.site_setup) {
this.context.router.history.push("/setup"); this.context.router.history.push("/setup");
} }
setupTippy(); setupTippy();
@ -313,13 +310,10 @@ export class Home extends Component<any, HomeState> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ let siteView = this.state.siteRes.site_view;
some: siteView => return this.state.siteRes.site_view.site.description.match({
siteView.site.description.match({ some: desc => `${siteView.site.name} - ${desc}`,
some: desc => `${siteView.site.name} - ${desc}`, none: siteView.site.name,
none: siteView.site.name,
}),
none: "Lemmy",
}); });
} }
@ -332,7 +326,7 @@ export class Home extends Component<any, HomeState> {
description={None} description={None}
image={None} image={None}
/> />
{this.state.siteRes.site_view.isSome() && ( {this.state.siteRes.site_view.local_site.site_setup && (
<div className="row"> <div className="row">
<main role="main" className="col-12 col-md-8"> <main role="main" className="col-12 col-md-8">
<div className="d-block d-md-none">{this.mobileView()}</div> <div className="d-block d-md-none">{this.mobileView()}</div>
@ -356,6 +350,7 @@ export class Home extends Component<any, HomeState> {
mobileView() { mobileView() {
let siteRes = this.state.siteRes; let siteRes = this.state.siteRes;
let siteView = siteRes.site_view;
return ( return (
<div className="row"> <div className="row">
<div className="col-12"> <div className="col-12">
@ -399,19 +394,15 @@ export class Home extends Component<any, HomeState> {
classes="icon-inline" classes="icon-inline"
/> />
</button> </button>
{this.state.showSidebarMobile && {this.state.showSidebarMobile && (
siteRes.site_view.match({ <SiteSidebar
some: siteView => ( site={siteView.site}
<SiteSidebar admins={Some(siteRes.admins)}
site={siteView.site} counts={Some(siteView.counts)}
admins={Some(siteRes.admins)} online={Some(siteRes.online)}
counts={Some(siteView.counts)} showLocal={showLocal(this.isoData)}
online={Some(siteRes.online)} />
showLocal={showLocal(this.isoData)} )}
/>
),
none: <></>,
})}
{this.state.showTrendingMobile && ( {this.state.showTrendingMobile && (
<div className="col-12 card border-secondary mb-3"> <div className="col-12 card border-secondary mb-3">
<div className="card-body">{this.trendingCommunities()}</div> <div className="card-body">{this.trendingCommunities()}</div>
@ -429,6 +420,7 @@ export class Home extends Component<any, HomeState> {
mySidebar() { mySidebar() {
let siteRes = this.state.siteRes; let siteRes = this.state.siteRes;
let siteView = siteRes.site_view;
return ( return (
<div> <div>
{!this.state.loading && ( {!this.state.loading && (
@ -441,18 +433,13 @@ export class Home extends Component<any, HomeState> {
{this.exploreCommunitiesButton()} {this.exploreCommunitiesButton()}
</div> </div>
</div> </div>
{siteRes.site_view.match({ <SiteSidebar
some: siteView => ( site={siteView.site}
<SiteSidebar admins={Some(siteRes.admins)}
site={siteView.site} counts={Some(siteView.counts)}
admins={Some(siteRes.admins)} online={Some(siteRes.online)}
counts={Some(siteView.counts)} showLocal={showLocal(this.isoData)}
online={Some(siteRes.online)} />
showLocal={showLocal(this.isoData)}
/>
),
none: <></>,
})}
{this.hasFollows && ( {this.hasFollows && (
<div className="card border-secondary mb-3"> <div className="card border-secondary mb-3">
<div className="card-body">{this.subscribedCommunities()}</div> <div className="card-body">{this.subscribedCommunities()}</div>
@ -749,7 +736,7 @@ export class Home extends Component<any, HomeState> {
this.setState({ trendingCommunities: data.communities }); this.setState({ trendingCommunities: data.communities });
} else if (op == UserOperation.EditSite) { } else if (op == UserOperation.EditSite) {
let data = wsJsonToRes<SiteResponse>(msg, SiteResponse); let data = wsJsonToRes<SiteResponse>(msg, SiteResponse);
this.setState(s => ((s.siteRes.site_view = Some(data.site_view)), s)); this.setState(s => ((s.siteRes.site_view = data.site_view), s));
toast(i18n.t("site_saved")); toast(i18n.t("site_saved"));
} else if (op == UserOperation.GetPosts) { } else if (op == UserOperation.GetPosts) {
let data = wsJsonToRes<GetPostsResponse>(msg, GetPostsResponse); let data = wsJsonToRes<GetPostsResponse>(msg, GetPostsResponse);

View file

@ -21,10 +21,7 @@ export class Instances extends Component<any, InstancesState> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return `${i18n.t("instances")} - ${this.state.siteRes.site_view.site.name}`;
some: siteView => `${i18n.t("instances")} - ${siteView.site.name}`,
none: "",
});
} }
render() { render() {

View file

@ -33,17 +33,10 @@ export class Legal extends Component<any, LegalState> {
description={None} description={None}
image={None} image={None}
/> />
{this.state.siteRes.site_view.match({ {this.state.siteRes.site_view.local_site.legal_information.match({
some: siteView => some: legal => (
siteView.site.legal_information.match({ <div className="md-div" dangerouslySetInnerHTML={mdToHtml(legal)} />
some: legal => ( ),
<div
className="md-div"
dangerouslySetInnerHTML={mdToHtml(legal)}
/>
),
none: <></>,
}),
none: <></>, none: <></>,
})} })}
</div> </div>

View file

@ -69,10 +69,7 @@ export class Login extends Component<any, State> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return `${i18n.t("login")} - ${this.state.siteRes.site_view.site.name}`;
some: siteView => `${i18n.t("login")} - ${siteView.site.name}`,
none: "",
});
} }
get isLemmyMl(): boolean { get isLemmyMl(): boolean {
@ -194,6 +191,8 @@ export class Login extends Component<any, State> {
let data = wsJsonToRes<LoginResponse>(msg, LoginResponse); let data = wsJsonToRes<LoginResponse>(msg, LoginResponse);
this.setState(this.emptyState); this.setState(this.emptyState);
UserService.Instance.login(data); UserService.Instance.login(data);
this.props.history.push("/");
location.reload();
} else if (op == UserOperation.PasswordReset) { } else if (op == UserOperation.PasswordReset) {
toast(i18n.t("reset_password_mail_sent")); toast(i18n.t("reset_password_mail_sent"));
} else if (op == UserOperation.GetSite) { } else if (op == UserOperation.GetSite) {

View file

@ -2,6 +2,7 @@ import { None, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { Helmet } from "inferno-helmet"; import { Helmet } from "inferno-helmet";
import { import {
GetSiteResponse,
LoginResponse, LoginResponse,
Register, Register,
toUndefined, toUndefined,
@ -13,7 +14,7 @@ import { Subscription } from "rxjs";
import { delay, retryWhen, take } from "rxjs/operators"; import { delay, retryWhen, take } from "rxjs/operators";
import { i18n } from "../../i18next"; import { i18n } from "../../i18next";
import { UserService, WebSocketService } from "../../services"; import { UserService, WebSocketService } from "../../services";
import { toast, wsClient } from "../../utils"; import { setIsoData, toast, wsClient } from "../../utils";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import { SiteForm } from "./site-form"; import { SiteForm } from "./site-form";
@ -21,10 +22,12 @@ interface State {
userForm: Register; userForm: Register;
doneRegisteringUser: boolean; doneRegisteringUser: boolean;
userLoading: boolean; userLoading: boolean;
siteRes: GetSiteResponse;
} }
export class Setup extends Component<any, State> { export class Setup extends Component<any, State> {
private subscription: Subscription; private subscription: Subscription;
private isoData = setIsoData(this.context);
private emptyState: State = { private emptyState: State = {
userForm: new Register({ userForm: new Register({
@ -41,6 +44,7 @@ export class Setup extends Component<any, State> {
}), }),
doneRegisteringUser: UserService.Instance.myUserInfo.isSome(), doneRegisteringUser: UserService.Instance.myUserInfo.isSome(),
userLoading: false, userLoading: false,
siteRes: this.isoData.site_res,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -75,7 +79,7 @@ export class Setup extends Component<any, State> {
{!this.state.doneRegisteringUser ? ( {!this.state.doneRegisteringUser ? (
this.registerUser() this.registerUser()
) : ( ) : (
<SiteForm site={None} showLocal /> <SiteForm siteRes={this.state.siteRes} showLocal />
)} )}
</div> </div>
</div> </div>

View file

@ -111,14 +111,14 @@ export class Signup extends Component<any, State> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ let siteView = this.state.siteRes.site_view;
some: siteView => `${this.titleName(siteView)} - ${siteView.site.name}`, return `${this.titleName(siteView)} - ${siteView.site.name}`;
none: "",
});
} }
titleName(siteView: SiteView): string { titleName(siteView: SiteView): string {
return i18n.t(siteView.site.private_instance ? "apply_to_join" : "sign_up"); return i18n.t(
siteView.local_site.private_instance ? "apply_to_join" : "sign_up"
);
} }
get isLemmyMl(): boolean { get isLemmyMl(): boolean {
@ -144,248 +144,238 @@ export class Signup extends Component<any, State> {
} }
registerForm() { registerForm() {
return this.state.siteRes.site_view.match({ let siteView = this.state.siteRes.site_view;
some: siteView => ( return (
<form onSubmit={linkEvent(this, this.handleRegisterSubmit)}> <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
<h5>{this.titleName(siteView)}</h5> <h5>{this.titleName(siteView)}</h5>
{this.isLemmyMl && (
<div className="form-group row">
<div className="mt-2 mb-0 alert alert-warning" role="alert">
<T i18nKey="lemmy_ml_registration_message">
#<a href={joinLemmyUrl}>#</a>
</T>
</div>
</div>
)}
{this.isLemmyMl && (
<div className="form-group row"> <div className="form-group row">
<label <div className="mt-2 mb-0 alert alert-warning" role="alert">
className="col-sm-2 col-form-label" <T i18nKey="lemmy_ml_registration_message">
htmlFor="register-username" #<a href={joinLemmyUrl}>#</a>
> </T>
{i18n.t("username")}
</label>
<div className="col-sm-10">
<input
type="text"
id="register-username"
className="form-control"
value={this.state.registerForm.username}
onInput={linkEvent(this, this.handleRegisterUsernameChange)}
required
minLength={3}
pattern="[a-zA-Z0-9_]+"
title={i18n.t("community_reqs")}
/>
</div> </div>
</div> </div>
)}
<div className="form-group row"> <div className="form-group row">
<label className="col-sm-2 col-form-label" htmlFor="register-email"> <label
{i18n.t("email")} className="col-sm-2 col-form-label"
</label> htmlFor="register-username"
<div className="col-sm-10"> >
<input {i18n.t("username")}
type="email" </label>
id="register-email"
className="form-control" <div className="col-sm-10">
placeholder={ <input
siteView.site.require_email_verification type="text"
? i18n.t("required") id="register-username"
: i18n.t("optional") className="form-control"
} value={this.state.registerForm.username}
value={toUndefined(this.state.registerForm.email)} onInput={linkEvent(this, this.handleRegisterUsernameChange)}
autoComplete="email" required
onInput={linkEvent(this, this.handleRegisterEmailChange)} minLength={3}
required={siteView.site.require_email_verification} pattern="[a-zA-Z0-9_]+"
minLength={3} title={i18n.t("community_reqs")}
/> />
{!siteView.site.require_email_verification &&
!this.state.registerForm.email
.map(validEmail)
.unwrapOr(true) && (
<div className="mt-2 mb-0 alert alert-warning" role="alert">
<Icon icon="alert-triangle" classes="icon-inline mr-2" />
{i18n.t("no_password_reset")}
</div>
)}
</div>
</div> </div>
</div>
<div className="form-group row"> <div className="form-group row">
<label <label className="col-sm-2 col-form-label" htmlFor="register-email">
className="col-sm-2 col-form-label" {i18n.t("email")}
htmlFor="register-password" </label>
> <div className="col-sm-10">
{i18n.t("password")} <input
</label> type="email"
<div className="col-sm-10"> id="register-email"
<input className="form-control"
type="password" placeholder={
id="register-password" siteView.local_site.require_email_verification
value={this.state.registerForm.password} ? i18n.t("required")
autoComplete="new-password" : i18n.t("optional")
onInput={linkEvent(this, this.handleRegisterPasswordChange)} }
minLength={10} value={toUndefined(this.state.registerForm.email)}
maxLength={60} autoComplete="email"
className="form-control" onInput={linkEvent(this, this.handleRegisterEmailChange)}
required required={siteView.local_site.require_email_verification}
/> minLength={3}
{this.state.registerForm.password && ( />
<div className={this.passwordColorClass}> {!siteView.local_site.require_email_verification &&
{i18n.t(this.passwordStrength as I18nKeys)} !this.state.registerForm.email.map(validEmail).unwrapOr(true) && (
<div className="mt-2 mb-0 alert alert-warning" role="alert">
<Icon icon="alert-triangle" classes="icon-inline mr-2" />
{i18n.t("no_password_reset")}
</div> </div>
)} )}
</div>
</div> </div>
</div>
<div className="form-group row"> <div className="form-group row">
<label <label
className="col-sm-2 col-form-label" className="col-sm-2 col-form-label"
htmlFor="register-verify-password" htmlFor="register-password"
> >
{i18n.t("verify_password")} {i18n.t("password")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
type="password" type="password"
id="register-verify-password" id="register-password"
value={this.state.registerForm.password_verify} value={this.state.registerForm.password}
autoComplete="new-password" autoComplete="new-password"
onInput={linkEvent( onInput={linkEvent(this, this.handleRegisterPasswordChange)}
this, minLength={10}
this.handleRegisterPasswordVerifyChange maxLength={60}
)} className="form-control"
maxLength={60} required
className="form-control" />
required {this.state.registerForm.password && (
/> <div className={this.passwordColorClass}>
</div> {i18n.t(this.passwordStrength as I18nKeys)}
</div>
)}
</div> </div>
</div>
{siteView.site.require_application && ( <div className="form-group row">
<> <label
<div className="form-group row"> className="col-sm-2 col-form-label"
<div className="offset-sm-2 col-sm-10"> htmlFor="register-verify-password"
<div className="mt-2 alert alert-warning" role="alert"> >
<Icon icon="alert-triangle" classes="icon-inline mr-2" /> {i18n.t("verify_password")}
{i18n.t("fill_out_application")} </label>
</div> <div className="col-sm-10">
{siteView.site.application_question.match({ <input
some: question => ( type="password"
<div id="register-verify-password"
className="md-div" value={this.state.registerForm.password_verify}
dangerouslySetInnerHTML={mdToHtml(question)} autoComplete="new-password"
/> onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
), maxLength={60}
none: <></>, className="form-control"
})} required
</div> />
</div> </div>
</div>
<div className="form-group row"> {siteView.local_site.require_application && (
<label <>
className="col-sm-2 col-form-label"
htmlFor="application_answer"
>
{i18n.t("answer")}
</label>
<div className="col-sm-10">
<MarkdownTextArea
initialContent={None}
initialLanguageId={None}
placeholder={None}
buttonTitle={None}
maxLength={None}
onContentChange={this.handleAnswerChange}
hideNavigationWarnings
allLanguages={[]}
/>
</div>
</div>
</>
)}
{this.state.captcha.isSome() && (
<div className="form-group row"> <div className="form-group row">
<label className="col-sm-2" htmlFor="register-captcha"> <div className="offset-sm-2 col-sm-10">
<span className="mr-2">{i18n.t("enter_code")}</span> <div className="mt-2 alert alert-warning" role="alert">
<button <Icon icon="alert-triangle" classes="icon-inline mr-2" />
type="button" {i18n.t("fill_out_application")}
className="btn btn-secondary" </div>
onClick={linkEvent(this, this.handleRegenCaptcha)} {siteView.local_site.application_question.match({
aria-label={i18n.t("captcha")} some: question => (
> <div
<Icon icon="refresh-cw" classes="icon-refresh-cw" /> className="md-div"
</button> dangerouslySetInnerHTML={mdToHtml(question)}
/>
),
none: <></>,
})}
</div>
</div>
<div className="form-group row">
<label
className="col-sm-2 col-form-label"
htmlFor="application_answer"
>
{i18n.t("answer")}
</label> </label>
{this.showCaptcha()} <div className="col-sm-10">
<div className="col-sm-6"> <MarkdownTextArea
<input initialContent={None}
type="text" initialLanguageId={None}
className="form-control" placeholder={None}
id="register-captcha" buttonTitle={None}
value={toUndefined(this.state.registerForm.captcha_answer)} maxLength={None}
onInput={linkEvent( onContentChange={this.handleAnswerChange}
this, hideNavigationWarnings
this.handleRegisterCaptchaAnswerChange allLanguages={[]}
)}
required
/> />
</div> </div>
</div> </div>
)} </>
{siteView.site.enable_nsfw && ( )}
<div className="form-group row">
<div className="col-sm-10"> {this.state.captcha.isSome() && (
<div className="form-check">
<input
className="form-check-input"
id="register-show-nsfw"
type="checkbox"
checked={this.state.registerForm.show_nsfw}
onChange={linkEvent(
this,
this.handleRegisterShowNsfwChange
)}
/>
<label
className="form-check-label"
htmlFor="register-show-nsfw"
>
{i18n.t("show_nsfw")}
</label>
</div>
</div>
</div>
)}
<input
tabIndex={-1}
autoComplete="false"
name="a_password"
type="text"
className="form-control honeypot"
id="register-honey"
value={toUndefined(this.state.registerForm.honeypot)}
onInput={linkEvent(this, this.handleHoneyPotChange)}
/>
<div className="form-group row"> <div className="form-group row">
<div className="col-sm-10"> <label className="col-sm-2" htmlFor="register-captcha">
<button type="submit" className="btn btn-secondary"> <span className="mr-2">{i18n.t("enter_code")}</span>
{this.state.registerLoading ? ( <button
<Spinner /> type="button"
) : ( className="btn btn-secondary"
this.titleName(siteView) onClick={linkEvent(this, this.handleRegenCaptcha)}
)} aria-label={i18n.t("captcha")}
>
<Icon icon="refresh-cw" classes="icon-refresh-cw" />
</button> </button>
</label>
{this.showCaptcha()}
<div className="col-sm-6">
<input
type="text"
className="form-control"
id="register-captcha"
value={toUndefined(this.state.registerForm.captcha_answer)}
onInput={linkEvent(
this,
this.handleRegisterCaptchaAnswerChange
)}
required
/>
</div> </div>
</div> </div>
</form> )}
), {siteView.local_site.enable_nsfw && (
none: <></>, <div className="form-group row">
}); <div className="col-sm-10">
<div className="form-check">
<input
className="form-check-input"
id="register-show-nsfw"
type="checkbox"
checked={this.state.registerForm.show_nsfw}
onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
/>
<label
className="form-check-label"
htmlFor="register-show-nsfw"
>
{i18n.t("show_nsfw")}
</label>
</div>
</div>
</div>
)}
<input
tabIndex={-1}
autoComplete="false"
name="a_password"
type="text"
className="form-control honeypot"
id="register-honey"
value={toUndefined(this.state.registerForm.honeypot)}
onInput={linkEvent(this, this.handleHoneyPotChange)}
/>
<div className="form-group row">
<div className="col-sm-10">
<button type="submit" className="btn btn-secondary">
{this.state.registerLoading ? (
<Spinner />
) : (
this.titleName(siteView)
)}
</button>
</div>
</div>
</form>
);
} }
showCaptcha() { showCaptcha() {
@ -545,6 +535,7 @@ export class Signup extends Component<any, State> {
if (data.jwt.isSome()) { if (data.jwt.isSome()) {
UserService.Instance.login(data); UserService.Instance.login(data);
this.props.history.push("/communities"); this.props.history.push("/communities");
location.reload();
} else { } else {
if (data.verify_email_sent) { if (data.verify_email_sent) {
toast(i18n.t("verify_email_sent")); toast(i18n.t("verify_email_sent"));

View file

@ -4,8 +4,8 @@ import { Prompt } from "inferno-router";
import { import {
CreateSite, CreateSite,
EditSite, EditSite,
GetSiteResponse,
ListingType, ListingType,
Site,
toUndefined, toUndefined,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { i18n } from "../../i18next";
@ -18,14 +18,13 @@ import {
} from "../../utils"; } from "../../utils";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form"; import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select";
import { ListingTypeSelect } from "../common/listing-type-select"; import { ListingTypeSelect } from "../common/listing-type-select";
import { MarkdownTextArea } from "../common/markdown-textarea"; import { MarkdownTextArea } from "../common/markdown-textarea";
interface SiteFormProps { interface SiteFormProps {
site: Option<Site>; // If a site is given, that means this is an edit siteRes: GetSiteResponse;
showLocal?: boolean; showLocal?: boolean;
onCancel?(): void;
onEdit?(): void;
} }
interface SiteFormState { interface SiteFormState {
@ -37,9 +36,9 @@ interface SiteFormState {
export class SiteForm extends Component<SiteFormProps, SiteFormState> { export class SiteForm extends Component<SiteFormProps, SiteFormState> {
private emptyState: SiteFormState = { private emptyState: SiteFormState = {
siteForm: new EditSite({ siteForm: new EditSite({
enable_downvotes: Some(true), enable_downvotes: None,
open_registration: Some(true), open_registration: None,
enable_nsfw: Some(true), enable_nsfw: None,
name: None, name: None,
icon: None, icon: None,
banner: None, banner: None,
@ -54,8 +53,32 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
description: None, description: None,
community_creation_admin_only: None, community_creation_admin_only: None,
application_email_admins: None, application_email_admins: None,
hide_modlog_mod_names: None,
discussion_languages: None,
slur_filter_regex: None,
actor_name_max_length: None,
rate_limit_message: None,
rate_limit_message_per_second: None,
rate_limit_comment: None,
rate_limit_comment_per_second: None,
rate_limit_image: None,
rate_limit_image_per_second: None,
rate_limit_post: None,
rate_limit_post_per_second: None,
rate_limit_register: None,
rate_limit_register_per_second: None,
rate_limit_search: None,
rate_limit_search_per_second: None,
federation_enabled: None,
federation_debug: None,
federation_worker_count: None,
federation_strict_allowlist: None,
federation_http_fetch_retry_limit: None,
captcha_enabled: None,
captcha_difficulty: None,
allowed_instances: None,
blocked_instances: None,
auth: undefined, auth: undefined,
hide_modlog_mod_names: Some(true),
}), }),
loading: false, loading: false,
themeList: None, themeList: None,
@ -79,35 +102,66 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
this.handleDefaultPostListingTypeChange = this.handleDefaultPostListingTypeChange =
this.handleDefaultPostListingTypeChange.bind(this); this.handleDefaultPostListingTypeChange.bind(this);
if (this.props.site.isSome()) { this.handleDiscussionLanguageChange =
let site = this.props.site.unwrap(); this.handleDiscussionLanguageChange.bind(this);
this.state = {
...this.state, let site = this.props.siteRes.site_view.site;
siteForm: new EditSite({ let ls = this.props.siteRes.site_view.local_site;
name: Some(site.name), let lsrl = this.props.siteRes.site_view.local_site_rate_limit;
sidebar: site.sidebar, this.state = {
description: site.description, ...this.state,
enable_downvotes: Some(site.enable_downvotes), siteForm: new EditSite({
open_registration: Some(site.open_registration), name: Some(site.name),
enable_nsfw: Some(site.enable_nsfw), sidebar: site.sidebar,
community_creation_admin_only: Some( description: site.description,
site.community_creation_admin_only enable_downvotes: Some(ls.enable_downvotes),
), open_registration: Some(ls.open_registration),
icon: site.icon, enable_nsfw: Some(ls.enable_nsfw),
banner: site.banner, community_creation_admin_only: Some(ls.community_creation_admin_only),
require_email_verification: Some(site.require_email_verification), icon: site.icon,
require_application: Some(site.require_application), banner: site.banner,
application_question: site.application_question, require_email_verification: Some(ls.require_email_verification),
private_instance: Some(site.private_instance), require_application: Some(ls.require_application),
default_theme: Some(site.default_theme), application_question: ls.application_question,
default_post_listing_type: Some(site.default_post_listing_type), private_instance: Some(ls.private_instance),
legal_information: site.legal_information, default_theme: Some(ls.default_theme),
application_email_admins: Some(site.application_email_admins), default_post_listing_type: Some(ls.default_post_listing_type),
hide_modlog_mod_names: site.hide_modlog_mod_names, legal_information: ls.legal_information,
auth: undefined, application_email_admins: Some(ls.application_email_admins),
}), hide_modlog_mod_names: Some(ls.hide_modlog_mod_names),
}; discussion_languages: Some(this.props.siteRes.discussion_languages),
} slur_filter_regex: ls.slur_filter_regex,
actor_name_max_length: Some(ls.actor_name_max_length),
rate_limit_message: Some(lsrl.message),
rate_limit_message_per_second: Some(lsrl.message_per_second),
rate_limit_comment: Some(lsrl.comment),
rate_limit_comment_per_second: Some(lsrl.comment_per_second),
rate_limit_image: Some(lsrl.image),
rate_limit_image_per_second: Some(lsrl.image_per_second),
rate_limit_post: Some(lsrl.post),
rate_limit_post_per_second: Some(lsrl.post_per_second),
rate_limit_register: Some(lsrl.register),
rate_limit_register_per_second: Some(lsrl.register_per_second),
rate_limit_search: Some(lsrl.search),
rate_limit_search_per_second: Some(lsrl.search_per_second),
federation_enabled: Some(ls.federation_enabled),
federation_debug: Some(ls.federation_debug),
federation_worker_count: Some(ls.federation_worker_count),
federation_strict_allowlist: Some(ls.federation_strict_allowlist),
federation_http_fetch_retry_limit: Some(
ls.federation_http_fetch_retry_limit
),
captcha_enabled: Some(ls.captcha_enabled),
captcha_difficulty: Some(ls.captcha_difficulty),
allowed_instances: this.props.siteRes.federated_instances.andThen(
f => f.allowed
),
blocked_instances: this.props.siteRes.federated_instances.andThen(
f => f.blocked
),
auth: undefined,
}),
};
} }
async componentDidMount() { async componentDidMount() {
@ -122,7 +176,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
componentDidUpdate() { componentDidUpdate() {
if ( if (
!this.state.loading && !this.state.loading &&
this.props.site.isNone() && !this.props.siteRes.site_view.local_site.site_setup &&
(this.state.siteForm.name || (this.state.siteForm.name ||
this.state.siteForm.sidebar || this.state.siteForm.sidebar ||
this.state.siteForm.application_question || this.state.siteForm.application_question ||
@ -139,12 +193,13 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
} }
render() { render() {
let siteSetup = this.props.siteRes.site_view.local_site.site_setup;
return ( return (
<> <>
<Prompt <Prompt
when={ when={
!this.state.loading && !this.state.loading &&
this.props.site.isNone() && !siteSetup &&
(this.state.siteForm.name || (this.state.siteForm.name ||
this.state.siteForm.sidebar || this.state.siteForm.sidebar ||
this.state.siteForm.application_question || this.state.siteForm.application_question ||
@ -154,7 +209,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
/> />
<form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}> <form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}>
<h5>{`${ <h5>{`${
this.props.site.isSome() siteSetup
? capitalizeFirstLetter(i18n.t("save")) ? capitalizeFirstLetter(i18n.t("save"))
: capitalizeFirstLetter(i18n.t("name")) : capitalizeFirstLetter(i18n.t("name"))
} ${i18n.t("your_site")}`}</h5> } ${i18n.t("your_site")}`}</h5>
@ -496,6 +551,487 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
</div> </div>
</div> </div>
</div> </div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-slur-filter-regex"
>
{i18n.t("slur_filter_regex")}
</label>
<div className="col-12">
<input
type="text"
id="create-site-slur-filter-regex"
placeholder="(word1|word2)"
className="form-control"
value={toUndefined(this.state.siteForm.slur_filter_regex)}
onInput={linkEvent(this, this.handleSiteSlurFilterRegex)}
minLength={3}
/>
</div>
</div>
<LanguageSelect
allLanguages={this.props.siteRes.all_languages}
selectedLanguageIds={this.state.siteForm.discussion_languages}
multiple={true}
onChange={this.handleDiscussionLanguageChange}
/>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-actor-name"
>
{i18n.t("actor_name_max_length")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-actor-name"
className="form-control"
min={5}
value={toUndefined(this.state.siteForm.actor_name_max_length)}
onInput={linkEvent(this, this.handleSiteActorNameMaxLength)}
/>
</div>
</div>
<div className="form-group row">
<div className="col-12">
<div className="form-check">
<input
className="form-check-input"
id="create-site-federation-enabled"
type="checkbox"
checked={toUndefined(this.state.siteForm.federation_enabled)}
onChange={linkEvent(this, this.handleSiteFederationEnabled)}
/>
<label
className="form-check-label"
htmlFor="create-site-federation-enabled"
>
{i18n.t("federation_enabled")}
</label>
</div>
</div>
</div>
{this.state.siteForm.federation_enabled.unwrapOr(false) && (
<>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-allowed-instances"
>
{i18n.t("allowed_instances")}
</label>
<div className="col-12">
<input
type="text"
placeholder="instance1.tld,instance2.tld"
id="create-site-allowed-instances"
className="form-control"
value={this.instancesToString(
this.state.siteForm.allowed_instances
)}
onInput={linkEvent(this, this.handleSiteAllowedInstances)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-blocked-instances"
>
{i18n.t("blocked_instances")}
</label>
<div className="col-12">
<input
type="text"
placeholder="instance1.tld,instance2.tld"
id="create-site-blocked-instances"
className="form-control"
value={this.instancesToString(
this.state.siteForm.blocked_instances
)}
onInput={linkEvent(this, this.handleSiteBlockedInstances)}
/>
</div>
</div>
<div className="form-group row">
<div className="col-12">
<div className="form-check">
<input
className="form-check-input"
id="create-site-federation-debug"
type="checkbox"
checked={toUndefined(
this.state.siteForm.federation_debug
)}
onChange={linkEvent(this, this.handleSiteFederationDebug)}
/>
<label
className="form-check-label"
htmlFor="create-site-federation-debug"
>
{i18n.t("federation_debug")}
</label>
</div>
</div>
</div>
<div className="form-group row">
<div className="col-12">
<div className="form-check">
<input
className="form-check-input"
id="create-site-federation-strict-allowlist"
type="checkbox"
checked={toUndefined(
this.state.siteForm.federation_strict_allowlist
)}
onChange={linkEvent(
this,
this.handleSiteFederationStrictAllowList
)}
/>
<label
className="form-check-label"
htmlFor="create-site-federation-strict-allowlist"
>
{i18n.t("federation_strict_allowlist")}
</label>
</div>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-federation-http-fetch-retry-limit"
>
{i18n.t("federation_http_fetch_retry_limit")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-federation-http-fetch-retry-limit"
className="form-control"
min={0}
value={toUndefined(
this.state.siteForm.federation_http_fetch_retry_limit
)}
onInput={linkEvent(
this,
this.handleSiteFederationHttpFetchRetryLimit
)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-federation-worker-count"
>
{i18n.t("federation_worker_count")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-federation-worker-count"
className="form-control"
min={0}
value={toUndefined(
this.state.siteForm.federation_worker_count
)}
onInput={linkEvent(
this,
this.handleSiteFederationWorkerCount
)}
/>
</div>
</div>
</>
)}
<div className="form-group row">
<div className="col-12">
<div className="form-check">
<input
className="form-check-input"
id="create-site-captcha-enabled"
type="checkbox"
checked={toUndefined(this.state.siteForm.captcha_enabled)}
onChange={linkEvent(this, this.handleSiteCaptchaEnabled)}
/>
<label
className="form-check-label"
htmlFor="create-site-captcha-enabled"
>
{i18n.t("captcha_enabled")}
</label>
</div>
</div>
</div>
{this.state.siteForm.captcha_enabled.unwrapOr(false) && (
<div className="form-group row">
<div className="col-12">
<label
className="form-check-label mr-2"
htmlFor="create-site-captcha-difficulty"
>
{i18n.t("captcha_difficulty")}
</label>
<select
id="create-site-captcha-difficulty"
value={toUndefined(this.state.siteForm.captcha_difficulty)}
onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
className="custom-select w-auto"
>
<option value="easy">{i18n.t("easy")}</option>
<option value="medium">{i18n.t("medium")}</option>
<option value="hard">{i18n.t("hard")}</option>
</select>
</div>
</div>
)}
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-message"
>
{i18n.t("rate_limit_message")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-message"
className="form-control"
min={0}
value={toUndefined(this.state.siteForm.rate_limit_message)}
onInput={linkEvent(this, this.handleSiteRateLimitMessage)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-message-per-second"
>
{i18n.t("per_second")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-message-per-second"
className="form-control"
min={0}
value={toUndefined(
this.state.siteForm.rate_limit_message_per_second
)}
onInput={linkEvent(
this,
this.handleSiteRateLimitMessagePerSecond
)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-post"
>
{i18n.t("rate_limit_post")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-post"
className="form-control"
min={0}
value={toUndefined(this.state.siteForm.rate_limit_post)}
onInput={linkEvent(this, this.handleSiteRateLimitPost)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-post-per-second"
>
{i18n.t("per_second")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-post-per-second"
className="form-control"
min={0}
value={toUndefined(
this.state.siteForm.rate_limit_post_per_second
)}
onInput={linkEvent(this, this.handleSiteRateLimitPostPerSecond)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-register"
>
{i18n.t("rate_limit_register")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-register"
className="form-control"
min={0}
value={toUndefined(this.state.siteForm.rate_limit_register)}
onInput={linkEvent(this, this.handleSiteRateLimitRegister)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-register-per-second"
>
{i18n.t("per_second")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-register-per-second"
className="form-control"
min={0}
value={toUndefined(
this.state.siteForm.rate_limit_register_per_second
)}
onInput={linkEvent(
this,
this.handleSiteRateLimitRegisterPerSecond
)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-image"
>
{i18n.t("rate_limit_image")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-image"
className="form-control"
min={0}
value={toUndefined(this.state.siteForm.rate_limit_image)}
onInput={linkEvent(this, this.handleSiteRateLimitImage)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-image-per-second"
>
{i18n.t("per_second")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-image-per-second"
className="form-control"
min={0}
value={toUndefined(
this.state.siteForm.rate_limit_image_per_second
)}
onInput={linkEvent(
this,
this.handleSiteRateLimitImagePerSecond
)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-comment"
>
{i18n.t("rate_limit_comment")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-comment"
className="form-control"
min={0}
value={toUndefined(this.state.siteForm.rate_limit_comment)}
onInput={linkEvent(this, this.handleSiteRateLimitComment)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-comment-per-second"
>
{i18n.t("per_second")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-comment-per-second"
className="form-control"
min={0}
value={toUndefined(
this.state.siteForm.rate_limit_comment_per_second
)}
onInput={linkEvent(
this,
this.handleSiteRateLimitCommentPerSecond
)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-search"
>
{i18n.t("rate_limit_search")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-search"
className="form-control"
min={0}
value={toUndefined(this.state.siteForm.rate_limit_search)}
onInput={linkEvent(this, this.handleSiteRateLimitSearch)}
/>
</div>
</div>
<div className="form-group row">
<label
className="col-12 col-form-label"
htmlFor="create-site-rate-limit-search-per-second"
>
{i18n.t("per_second")}
</label>
<div className="col-12">
<input
type="number"
id="create-site-rate-limit-search-per-second"
className="form-control"
min={0}
value={toUndefined(
this.state.siteForm.rate_limit_search_per_second
)}
onInput={linkEvent(
this,
this.handleSiteRateLimitSearchPerSecond
)}
/>
</div>
</div>
<div className="form-group row"> <div className="form-group row">
<div className="col-12"> <div className="col-12">
<button <button
@ -505,21 +1041,12 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
> >
{this.state.loading ? ( {this.state.loading ? (
<Spinner /> <Spinner />
) : this.props.site.isSome() ? ( ) : siteSetup ? (
capitalizeFirstLetter(i18n.t("save")) capitalizeFirstLetter(i18n.t("save"))
) : ( ) : (
capitalizeFirstLetter(i18n.t("create")) capitalizeFirstLetter(i18n.t("create"))
)} )}
</button> </button>
{this.props.site.isSome() && (
<button
type="button"
className="btn btn-secondary"
onClick={linkEvent(this, this.handleCancel)}
>
{i18n.t("cancel")}
</button>
)}
</div> </div>
</div> </div>
</form> </form>
@ -532,9 +1059,8 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
i.setState({ loading: true }); i.setState({ loading: true });
i.setState(s => ((s.siteForm.auth = auth().unwrap()), s)); i.setState(s => ((s.siteForm.auth = auth().unwrap()), s));
if (i.props.site.isSome()) { if (i.props.siteRes.site_view.local_site.site_setup) {
WebSocketService.Instance.send(wsClient.editSite(i.state.siteForm)); WebSocketService.Instance.send(wsClient.editSite(i.state.siteForm));
i.props.onEdit();
} else { } else {
let sForm = i.state.siteForm; let sForm = i.state.siteForm;
let form = new CreateSite({ let form = new CreateSite({
@ -556,12 +1082,52 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
application_email_admins: sForm.application_email_admins, application_email_admins: sForm.application_email_admins,
auth: auth().unwrap(), auth: auth().unwrap(),
hide_modlog_mod_names: sForm.hide_modlog_mod_names, hide_modlog_mod_names: sForm.hide_modlog_mod_names,
legal_information: sForm.legal_information,
slur_filter_regex: sForm.slur_filter_regex,
actor_name_max_length: sForm.actor_name_max_length,
rate_limit_message: sForm.rate_limit_message,
rate_limit_message_per_second: sForm.rate_limit_message_per_second,
rate_limit_comment: sForm.rate_limit_comment,
rate_limit_comment_per_second: sForm.rate_limit_comment_per_second,
rate_limit_image: sForm.rate_limit_image,
rate_limit_image_per_second: sForm.rate_limit_image_per_second,
rate_limit_post: sForm.rate_limit_post,
rate_limit_post_per_second: sForm.rate_limit_post_per_second,
rate_limit_register: sForm.rate_limit_register,
rate_limit_register_per_second: sForm.rate_limit_register_per_second,
rate_limit_search: sForm.rate_limit_search,
rate_limit_search_per_second: sForm.rate_limit_search_per_second,
federation_enabled: sForm.federation_enabled,
federation_debug: sForm.federation_debug,
federation_worker_count: sForm.federation_worker_count,
federation_strict_allowlist: sForm.federation_strict_allowlist,
federation_http_fetch_retry_limit:
sForm.federation_http_fetch_retry_limit,
captcha_enabled: sForm.captcha_enabled,
captcha_difficulty: sForm.captcha_difficulty,
allowed_instances: sForm.allowed_instances,
blocked_instances: sForm.blocked_instances,
discussion_languages: sForm.discussion_languages,
}); });
WebSocketService.Instance.send(wsClient.createSite(form)); WebSocketService.Instance.send(wsClient.createSite(form));
} }
i.setState(i.state); i.setState(i.state);
} }
instancesToString(opt: Option<string[]>): string {
return opt.map(list => list.join(",")).unwrapOr("");
}
handleSiteAllowedInstances(i: SiteForm, event: any) {
let list = splitToList(event.target.value);
i.setState(s => ((s.siteForm.allowed_instances = list), s));
}
handleSiteBlockedInstances(i: SiteForm, event: any) {
let list = splitToList(event.target.value);
i.setState(s => ((s.siteForm.blocked_instances = list), s));
}
handleSiteNameChange(i: SiteForm, event: any) { handleSiteNameChange(i: SiteForm, event: any) {
i.state.siteForm.name = Some(event.target.value); i.state.siteForm.name = Some(event.target.value);
i.setState(i.state); i.setState(i.state);
@ -634,10 +1200,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
i.setState(i.state); i.setState(i.state);
} }
handleCancel(i: SiteForm) {
i.props.onCancel();
}
handleIconUpload(url: string) { handleIconUpload(url: string) {
this.setState(s => ((s.siteForm.icon = Some(url)), s)); this.setState(s => ((s.siteForm.icon = Some(url)), s));
} }
@ -654,6 +1216,180 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
this.setState(s => ((s.siteForm.banner = Some("")), s)); this.setState(s => ((s.siteForm.banner = Some("")), s));
} }
handleSiteSlurFilterRegex(i: SiteForm, event: any) {
i.setState(
s => ((s.siteForm.slur_filter_regex = Some(event.target.value)), s)
);
}
handleSiteActorNameMaxLength(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.actor_name_max_length = Some(Number(event.target.value))), s
)
);
}
handleSiteRateLimitMessage(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.rate_limit_message = Some(Number(event.target.value))), s
)
);
}
handleSiteRateLimitMessagePerSecond(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.rate_limit_message_per_second = Some(
Number(event.target.value)
)),
s
)
);
}
handleSiteRateLimitPost(i: SiteForm, event: any) {
i.setState(
s => ((s.siteForm.rate_limit_post = Some(Number(event.target.value))), s)
);
}
handleSiteRateLimitPostPerSecond(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.rate_limit_post_per_second = Some(
Number(event.target.value)
)),
s
)
);
}
handleSiteRateLimitImage(i: SiteForm, event: any) {
i.setState(
s => ((s.siteForm.rate_limit_image = Some(Number(event.target.value))), s)
);
}
handleSiteRateLimitImagePerSecond(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.rate_limit_image_per_second = Some(
Number(event.target.value)
)),
s
)
);
}
handleSiteRateLimitComment(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.rate_limit_comment = Some(Number(event.target.value))), s
)
);
}
handleSiteRateLimitCommentPerSecond(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.rate_limit_comment_per_second = Some(
Number(event.target.value)
)),
s
)
);
}
handleSiteRateLimitSearch(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.rate_limit_search = Some(Number(event.target.value))), s
)
);
}
handleSiteRateLimitSearchPerSecond(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.rate_limit_search_per_second = Some(
Number(event.target.value)
)),
s
)
);
}
handleSiteRateLimitRegister(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.rate_limit_register = Some(Number(event.target.value))), s
)
);
}
handleSiteRateLimitRegisterPerSecond(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.rate_limit_register_per_second = Some(
Number(event.target.value)
)),
s
)
);
}
handleSiteFederationEnabled(i: SiteForm, event: any) {
i.state.siteForm.federation_enabled = Some(event.target.checked);
i.setState(i.state);
}
handleSiteFederationDebug(i: SiteForm, event: any) {
i.state.siteForm.federation_debug = Some(event.target.checked);
i.setState(i.state);
}
handleSiteFederationStrictAllowList(i: SiteForm, event: any) {
i.state.siteForm.federation_strict_allowlist = Some(event.target.checked);
i.setState(i.state);
}
handleSiteFederationWorkerCount(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.federation_worker_count = Some(Number(event.target.value))),
s
)
);
}
handleSiteFederationHttpFetchRetryLimit(i: SiteForm, event: any) {
i.setState(
s => (
(s.siteForm.federation_http_fetch_retry_limit = Some(
Number(event.target.value)
)),
s
)
);
}
handleSiteCaptchaEnabled(i: SiteForm, event: any) {
i.state.siteForm.captcha_enabled = Some(event.target.checked);
i.setState(i.state);
}
handleSiteCaptchaDifficulty(i: SiteForm, event: any) {
i.setState(
s => ((s.siteForm.captcha_difficulty = Some(event.target.value)), s)
);
}
handleDiscussionLanguageChange(val: number[]) {
this.setState(s => ((s.siteForm.discussion_languages = Some(val)), s));
}
handleDefaultPostListingTypeChange(val: ListingType) { handleDefaultPostListingTypeChange(val: ListingType) {
this.setState( this.setState(
s => ( s => (
@ -665,3 +1401,12 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
); );
} }
} }
function splitToList(commaList: string): Option<string[]> {
if (commaList !== "") {
let list = commaList.trim().split(",");
return Some(list);
} else {
return Some([]);
}
}

View file

@ -1,13 +1,12 @@
import { None, Option, Some } from "@sniptt/monads"; import { None, Option } from "@sniptt/monads";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router"; import { Link } from "inferno-router";
import { PersonViewSafe, Site, SiteAggregates } from "lemmy-js-client"; import { PersonViewSafe, Site, SiteAggregates } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { i18n } from "../../i18next";
import { amAdmin, mdToHtml, numToSI } from "../../utils"; import { mdToHtml, numToSI } from "../../utils";
import { BannerIconHeader } from "../common/banner-icon-header"; import { BannerIconHeader } from "../common/banner-icon-header";
import { Icon } from "../common/icon"; import { Icon } from "../common/icon";
import { PersonListing } from "../person/person-listing"; import { PersonListing } from "../person/person-listing";
import { SiteForm } from "./site-form";
interface SiteSidebarProps { interface SiteSidebarProps {
site: Site; site: Site;
@ -19,58 +18,40 @@ interface SiteSidebarProps {
interface SiteSidebarState { interface SiteSidebarState {
collapsed: boolean; collapsed: boolean;
showEdit: boolean;
} }
export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> { export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
private emptyState: SiteSidebarState = { private emptyState: SiteSidebarState = {
collapsed: false, collapsed: false,
showEdit: false,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
super(props, context); super(props, context);
this.state = this.emptyState; this.state = this.emptyState;
this.handleEditCancel = this.handleEditCancel.bind(this);
this.handleEditSite = this.handleEditSite.bind(this);
} }
render() { render() {
let site = this.props.site;
return ( return (
<div className="card border-secondary mb-3"> <div className="card border-secondary mb-3">
<div className="card-body"> <div className="card-body">
{!this.state.showEdit ? ( <div>
<div> <div className="mb-2">{this.siteName()}</div>
<div className="mb-2"> {!this.state.collapsed && (
{this.siteName()} <>
{this.props.admins.isSome() && this.adminButtons()} <BannerIconHeader banner={this.props.site.banner} icon={None} />
</div> {this.siteInfo()}
{!this.state.collapsed && ( </>
<> )}
<BannerIconHeader banner={site.banner} icon={None} /> </div>
{this.siteInfo()}
</>
)}
</div>
) : (
<SiteForm
site={Some(site)}
showLocal={this.props.showLocal}
onEdit={this.handleEditSite}
onCancel={this.handleEditCancel}
/>
)}
</div> </div>
</div> </div>
); );
} }
siteName() { siteName() {
let site = this.props.site;
return ( return (
<h5 className="mb-0 d-inline"> <h5 className="mb-0 d-inline">
{site.name} {this.props.site.name}
<button <button
className="btn btn-sm text-muted" className="btn btn-sm text-muted"
onClick={linkEvent(this, this.handleCollapseSidebar)} onClick={linkEvent(this, this.handleCollapseSidebar)}
@ -111,25 +92,6 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
); );
} }
adminButtons() {
return (
amAdmin() && (
<ul className="list-inline mb-1 text-muted font-weight-bold">
<li className="list-inline-item-action">
<button
className="btn btn-link d-inline-block text-muted"
onClick={linkEvent(this, this.handleEditClick)}
aria-label={i18n.t("edit")}
data-tippy-content={i18n.t("edit")}
>
<Icon icon="edit" classes="icon-inline" />
</button>
</li>
</ul>
)
);
}
siteSidebar(sidebar: string) { siteSidebar(sidebar: string) {
return ( return (
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(sidebar)} /> <div className="md-div" dangerouslySetInnerHTML={mdToHtml(sidebar)} />
@ -248,16 +210,4 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
handleCollapseSidebar(i: SiteSidebar) { handleCollapseSidebar(i: SiteSidebar) {
i.setState({ collapsed: !i.state.collapsed }); i.setState({ collapsed: !i.state.collapsed });
} }
handleEditClick(i: SiteSidebar) {
i.setState({ showEdit: true });
}
handleEditSite() {
this.setState({ showEdit: false });
}
handleEditCancel() {
this.setState({ showEdit: false });
}
} }

View file

@ -632,10 +632,7 @@ export class Modlog extends Component<any, ModlogState> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return `Modlog - ${this.state.siteRes.site_view.site.name}`;
some: siteView => `Modlog - ${siteView.site.name}`,
none: "",
});
} }
render() { render() {
@ -708,21 +705,18 @@ export class Modlog extends Component<any, ModlogState> {
</option> </option>
</select> </select>
</div> </div>
{this.state.siteRes.site_view.match({ {!this.state.siteRes.site_view.local_site
some: site_view => .hide_modlog_mod_names && (
!site_view.site.hide_modlog_mod_names.unwrapOr(false) && ( <div className="form-group col-sm-6">
<div className="form-group col-sm-6"> <select
<select id="filter-mod"
id="filter-mod" className="form-control"
className="form-control" value={toUndefined(this.state.filter_mod)}
value={toUndefined(this.state.filter_mod)} >
> <option>{i18n.t("filter_by_mod")}</option>
<option>{i18n.t("filter_by_mod")}</option> </select>
</select> </div>
</div> )}
),
none: <></>,
})}
<div className="form-group col-sm-6"> <div className="form-group col-sm-6">
<select <select
id="filter-user" id="filter-user"

View file

@ -153,15 +153,11 @@ export class Inbox extends Component<any, InboxState> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return UserService.Instance.myUserInfo.match({
some: siteView => some: mui =>
UserService.Instance.myUserInfo.match({ `@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${
some: mui => this.state.siteRes.site_view.site.name
`@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${ }`,
siteView.site.name
}`,
none: "",
}),
none: "", none: "",
}); });
} }

View file

@ -58,10 +58,9 @@ export class PasswordChange extends Component<any, State> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return `${i18n.t("password_change")} - ${
some: siteView => `${i18n.t("password_change")} - ${siteView.site.name}`, this.state.siteRes.site_view.site.name
none: "", }`;
});
} }
render() { render() {
@ -164,6 +163,7 @@ export class PasswordChange extends Component<any, State> {
this.setState(this.emptyState); this.setState(this.emptyState);
UserService.Instance.login(data); UserService.Instance.login(data);
this.props.history.push("/"); this.props.history.push("/");
location.reload();
} }
} }
} }

View file

@ -238,13 +238,9 @@ export class Profile extends Component<any, ProfileState> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return this.state.personRes.match({
some: siteView => some: res =>
this.state.personRes.match({ `@${res.person_view.person.name} - ${this.state.siteRes.site_view.site.name}`,
some: res =>
`@${res.person_view.person.name} - ${siteView.site.name}`,
none: "",
}),
none: "", none: "",
}); });
} }

View file

@ -98,15 +98,11 @@ export class RegistrationApplications extends Component<
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return UserService.Instance.myUserInfo.match({
some: siteView => some: mui =>
UserService.Instance.myUserInfo.match({ `@${mui.local_user_view.person.name} ${i18n.t(
some: mui => "registration_applications"
`@${mui.local_user_view.person.name} ${i18n.t( )} - ${this.state.siteRes.site_view.site.name}`,
"registration_applications"
)} - ${siteView.site.name}`,
none: "",
}),
none: "", none: "",
}); });
} }

View file

@ -150,15 +150,11 @@ export class Reports extends Component<any, ReportsState> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return UserService.Instance.myUserInfo.match({
some: siteView => some: mui =>
UserService.Instance.myUserInfo.match({ `@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${
some: mui => this.state.siteRes.site_view.site.name
`@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${ }`,
siteView.site.name
}`,
none: "",
}),
none: "", none: "",
}); });
} }

View file

@ -1214,6 +1214,7 @@ export class Settings extends Component<any, SettingsState> {
} else if (op == UserOperation.SaveUserSettings) { } else if (op == UserOperation.SaveUserSettings) {
let data = wsJsonToRes<LoginResponse>(msg, LoginResponse); let data = wsJsonToRes<LoginResponse>(msg, LoginResponse);
UserService.Instance.login(data); UserService.Instance.login(data);
location.reload();
this.setState({ saveUserSettingsLoading: false }); this.setState({ saveUserSettingsLoading: false });
toast(i18n.t("saved")); toast(i18n.t("saved"));
window.scrollTo(0, 0); window.scrollTo(0, 0);

View file

@ -58,10 +58,9 @@ export class VerifyEmail extends Component<any, State> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return `${i18n.t("verify_email")} - ${
some: siteView => `${i18n.t("verify_email")} - ${siteView.site.name}`, this.state.siteRes.site_view.site.name
none: "", }`;
});
} }
render() { render() {

View file

@ -119,10 +119,9 @@ export class CreatePost extends Component<any, CreatePostState> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ return `${i18n.t("create_post")} - ${
some: siteView => `${i18n.t("create_post")} - ${siteView.site.name}`, this.state.siteRes.site_view.site.name
none: "", }`;
});
} }
render() { render() {

View file

@ -317,11 +317,7 @@ export class Post extends Component<any, PostState> {
get documentTitle(): string { get documentTitle(): string {
return this.state.postRes.match({ return this.state.postRes.match({
some: res => some: res =>
this.state.siteRes.site_view.match({ `${res.post_view.post.name} - ${this.state.siteRes.site_view.site.name}`,
some: siteView =>
`${res.post_view.post.name} - ${siteView.site.name}`,
none: "",
}),
none: "", none: "",
}); });
} }

View file

@ -379,13 +379,10 @@ export class Search extends Component<any, SearchState> {
} }
get documentTitle(): string { get documentTitle(): string {
return this.state.siteRes.site_view.match({ let siteName = this.state.siteRes.site_view.site.name;
some: siteView => return this.state.q
this.state.q ? `${i18n.t("search")} - ${this.state.q} - ${siteName}`
? `${i18n.t("search")} - ${this.state.q} - ${siteView.site.name}` : `${i18n.t("search")} - ${siteName}`;
: `${i18n.t("search")} - ${siteView.site.name}`,
none: "",
});
} }
render() { render() {

View file

@ -42,7 +42,6 @@ export class UserService {
toast(i18n.t("logged_in")); toast(i18n.t("logged_in"));
IsomorphicCookie.save("jwt", jwt, { expires, secure: isHttps }); IsomorphicCookie.save("jwt", jwt, { expires, secure: isHttps });
this.setJwtInfo(); this.setJwtInfo();
location.reload();
}, },
none: void 0, none: void 0,
}); });

View file

@ -1425,11 +1425,11 @@ export function auth(throwErr = true): Result<string, string> {
} }
export function enableDownvotes(siteRes: GetSiteResponse): boolean { export function enableDownvotes(siteRes: GetSiteResponse): boolean {
return siteRes.site_view.map(s => s.site.enable_downvotes).unwrapOr(true); return siteRes.site_view.local_site.enable_downvotes;
} }
export function enableNsfw(siteRes: GetSiteResponse): boolean { export function enableNsfw(siteRes: GetSiteResponse): boolean {
return siteRes.site_view.map(s => s.site.enable_nsfw).unwrapOr(false); return siteRes.site_view.local_site.enable_nsfw;
} }
export function postToCommentSortType(sort: SortType): CommentSortType { export function postToCommentSortType(sort: SortType): CommentSortType {
@ -1467,9 +1467,7 @@ export function canCreateCommunity(
siteRes: GetSiteResponse, siteRes: GetSiteResponse,
myUserInfo = UserService.Instance.myUserInfo myUserInfo = UserService.Instance.myUserInfo
): boolean { ): boolean {
let adminOnly = siteRes.site_view let adminOnly = siteRes.site_view.local_site.community_creation_admin_only;
.map(s => s.site.community_creation_admin_only)
.unwrapOr(false);
return !adminOnly || amAdmin(myUserInfo); return !adminOnly || amAdmin(myUserInfo);
} }

View file

@ -19,6 +19,7 @@
"noUnusedParameters": true, "noUnusedParameters": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"strictNullChecks": false,
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true
}, },
"include": [ "include": [

View file

@ -4879,10 +4879,10 @@ lcid@^1.0.0:
dependencies: dependencies:
invert-kv "^1.0.0" invert-kv "^1.0.0"
lemmy-js-client@0.17.0-rc.46: lemmy-js-client@0.17.0-rc.51:
version "0.17.0-rc.46" version "0.17.0-rc.51"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.46.tgz#c2820821ca46394fd17d1045e54c00a04b15700c" resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.51.tgz#adf554b8837741bc9bb419df090744fc2ef8a1fa"
integrity sha512-9HqKKsvToSB397ywXpl0jPa7KIhDaULWel0g35CgmfOkylvuTlpF8UZW20vMXr02Br9qvbRf0hRvi9s5uaji+Q== integrity sha512-AGXzQptVrdYim/5YrpAnlqAElZl5aTwqZcwffTrzXs4tL91b/APkdoPLUKASGt/5lRng2CP4cQTbykldZyjQRA==
levn@^0.4.1: levn@^0.4.1:
version "0.4.1" version "0.4.1"