--- /dev/null
+FROM node:alpine as builder\r
+RUN apk update && apk add curl yarn python3 build-base gcc wget git --no-cache\r
+\r
+WORKDIR /usr/src/app\r
+\r
+# Cache deps\r
+COPY package.json yarn.lock ./\r
+RUN yarn install --ignore-scripts --prefer-offline --pure-lockfile\r
+\r
+# Build\r
+COPY generate_translations.js \\r
+ tsconfig.json \\r
+ webpack.config.js \\r
+ .babelrc \\r
+ ./\r
+\r
+COPY lemmy-translations lemmy-translations\r
+COPY src src\r
+\r
+# Set UI version \r
+RUN echo "export const VERSION = 'dev';" > "src/shared/version.ts"\r
+\r
+RUN yarn install --ignore-scripts --prefer-offline\r
+RUN yarn build:dev\r
+\r
+FROM node:alpine as runner\r
+COPY --from=builder /usr/src/app/dist /app/dist\r
+COPY --from=builder /usr/src/app/node_modules /app/node_modules\r
+\r
+EXPOSE 1234\r
+WORKDIR /app\r
+CMD node dist/js/server.js\r
-Subproject commit 94b9b5debdaa40facf13a852cb096ef0cbd34ad7
+Subproject commit 46f4b3e8676c23d4a8100ce78330eceed7bcf053
"@babel/preset-typescript": "^7.18.6",
"@babel/runtime": "^7.18.9",
"@sniptt/monads": "^0.5.10",
- "@typescript-eslint/parser": "^5.31.0",
"autosize": "^5.0.1",
"babel-loader": "^8.2.5",
"babel-plugin-inferno": "^6.5.0",
"inferno-server": "^8.0.3",
"isomorphic-cookie": "^1.2.4",
"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-container": "^3.0.0",
"markdown-it-footnote": "^3.0.3",
"@types/node-fetch": "^2.6.2",
"@types/serialize-javascript": "^5.0.1",
"@typescript-eslint/eslint-plugin": "^5.31.0",
+ "@typescript-eslint/parser": "^5.31.0",
"bootstrap": "^5.2.0",
"bootswatch": "^5.2.0",
"eslint": "^8.20.0",
<>
<Provider i18next={i18n}>
<div>
- <Theme defaultTheme={siteView.map(s => s.site.default_theme)} />
- {siteView
- .andThen(s => s.site.icon)
- .match({
- some: icon => (
- <Helmet>
- <link
- id="favicon"
- rel="shortcut icon"
- type="image/x-icon"
- href={icon || favIconUrl}
- />
- <link rel="apple-touch-icon" href={icon || favIconPngUrl} />
- </Helmet>
- ),
- none: <></>,
- })}
+ <Theme defaultTheme={siteView.local_site.default_theme} />
+ {siteView.site.icon.match({
+ some: icon => (
+ <Helmet>
+ <link
+ id="favicon"
+ rel="shortcut icon"
+ type="image/x-icon"
+ href={icon || favIconUrl}
+ />
+ <link rel="apple-touch-icon" href={icon || favIconPngUrl} />
+ </Helmet>
+ ),
+ none: <></>,
+ })}
<Navbar siteRes={siteRes} />
<div className="mt-4 p-0 fl-1">
<Switch>
{i18n.t("modlog")}
</NavLink>
</li>
- {this.props.site.site_view
- .andThen(s => s.site.legal_information)
- .isSome() && (
+ {this.props.site.site_view.local_site.legal_information.isSome() && (
<li className="nav-item">
<NavLink className="nav-link" to="/legal">
{i18n.t("legal_information")}
})
);
- if (this.props.siteRes.site_view.isSome()) {
- this.fetchUnreads();
- }
+ this.fetchUnreads();
}
this.requestNotificationPermission();
// TODO class active corresponding to current page
navbar() {
+ let siteView = this.props.siteRes.site_view;
return (
<nav className="navbar navbar-expand-md navbar-light shadow-sm p-0 px-3">
<div className="container-lg">
- {this.props.siteRes.site_view.match({
- some: siteView => (
- <NavLink
- to="/"
- onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
- 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 />,
- none: <></>,
- })}
- {siteView.site.name}
- </NavLink>
- ),
- none: <></>,
- })}
+ <NavLink
+ to="/"
+ onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+ 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 />,
+ none: <></>,
+ })}
+ {siteView.site.name}
+ </NavLink>
{UserService.Instance.myUserInfo.isSome() && (
<>
<ul className="navbar-nav ml-auto">
-import { Option } from "@sniptt/monads";
import { Component } from "inferno";
import { Helmet } from "inferno-helmet";
import { UserService } from "../../services";
interface Props {
- defaultTheme: Option<string>;
+ defaultTheme: string;
}
export class Theme extends Component<Props> {
/>
</Helmet>
);
- } else if (
- this.props.defaultTheme.isSome() &&
- this.props.defaultTheme.unwrap() != "browser"
- ) {
+ } else if (this.props.defaultTheme != "browser") {
return (
<Helmet>
<link
rel="stylesheet"
type="text/css"
- href={`/css/themes/${this.props.defaultTheme.unwrap()}.css`}
+ href={`/css/themes/${this.props.defaultTheme}.css`}
/>
</Helmet>
);
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView => `${i18n.t("communities")} - ${siteView.site.name}`,
- none: "",
- });
+ return `${i18n.t("communities")} - ${
+ this.state.siteRes.site_view.site.name
+ }`;
}
render() {
get documentTitle(): string {
return this.state.communityRes.match({
some: res =>
- this.state.siteRes.site_view.match({
- some: siteView =>
- `${res.community_view.community.title} - ${siteView.site.name}`,
- none: "",
- }),
+ `${res.community_view.community.title} - ${this.state.siteRes.site_view.site.name}`,
none: "",
});
}
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView => `${i18n.t("create_community")} - ${siteView.site.name}`,
- none: "",
- });
+ return `${i18n.t("create_community")} - ${
+ this.state.siteRes.site_view.site.name
+ }`;
}
render() {
-import { None, Some } from "@sniptt/monads";
+import { None } from "@sniptt/monads";
import autosize from "autosize";
import { Component, linkEvent } from "inferno";
import {
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView => `${i18n.t("admin_settings")} - ${siteView.site.name}`,
- none: "",
- });
+ return `${i18n.t("admin_settings")} - ${
+ this.state.siteRes.site_view.site.name
+ }`;
}
render() {
description={None}
image={None}
/>
- {this.state.siteRes.site_view.match({
- some: siteView => (
- <SiteForm
- site={Some(siteView.site)}
- showLocal={showLocal(this.isoData)}
- />
- ),
- none: <></>,
- })}
+ <SiteForm
+ siteRes={this.state.siteRes}
+ showLocal={showLocal(this.isoData)}
+ />
</div>
<div className="col-12 col-md-6">
{this.admins()}
return;
} else if (op == UserOperation.EditSite) {
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"));
} else if (op == UserOperation.GetBannedPersons) {
let data = wsJsonToRes<BannedPersonsResponse>(msg, BannedPersonsResponse);
listingType: getListingTypeFromProps(
this.props,
ListingType[
- this.isoData.site_res.site_view.match({
- some: type_ => type_.site.default_post_listing_type,
- none: ListingType.Local,
- })
+ this.isoData.site_res.site_view.local_site.default_post_listing_type
]
),
dataType: getDataTypeFromProps(this.props),
componentDidMount() {
// 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");
}
setupTippy();
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView =>
- siteView.site.description.match({
- some: desc => `${siteView.site.name} - ${desc}`,
- none: siteView.site.name,
- }),
- none: "Lemmy",
+ let siteView = this.state.siteRes.site_view;
+ return this.state.siteRes.site_view.site.description.match({
+ some: desc => `${siteView.site.name} - ${desc}`,
+ none: siteView.site.name,
});
}
description={None}
image={None}
/>
- {this.state.siteRes.site_view.isSome() && (
+ {this.state.siteRes.site_view.local_site.site_setup && (
<div className="row">
<main role="main" className="col-12 col-md-8">
<div className="d-block d-md-none">{this.mobileView()}</div>
mobileView() {
let siteRes = this.state.siteRes;
+ let siteView = siteRes.site_view;
return (
<div className="row">
<div className="col-12">
classes="icon-inline"
/>
</button>
- {this.state.showSidebarMobile &&
- siteRes.site_view.match({
- some: siteView => (
- <SiteSidebar
- site={siteView.site}
- admins={Some(siteRes.admins)}
- counts={Some(siteView.counts)}
- online={Some(siteRes.online)}
- showLocal={showLocal(this.isoData)}
- />
- ),
- none: <></>,
- })}
+ {this.state.showSidebarMobile && (
+ <SiteSidebar
+ site={siteView.site}
+ admins={Some(siteRes.admins)}
+ counts={Some(siteView.counts)}
+ online={Some(siteRes.online)}
+ showLocal={showLocal(this.isoData)}
+ />
+ )}
{this.state.showTrendingMobile && (
<div className="col-12 card border-secondary mb-3">
<div className="card-body">{this.trendingCommunities()}</div>
mySidebar() {
let siteRes = this.state.siteRes;
+ let siteView = siteRes.site_view;
return (
<div>
{!this.state.loading && (
{this.exploreCommunitiesButton()}
</div>
</div>
- {siteRes.site_view.match({
- some: siteView => (
- <SiteSidebar
- site={siteView.site}
- admins={Some(siteRes.admins)}
- counts={Some(siteView.counts)}
- online={Some(siteRes.online)}
- showLocal={showLocal(this.isoData)}
- />
- ),
- none: <></>,
- })}
+ <SiteSidebar
+ site={siteView.site}
+ admins={Some(siteRes.admins)}
+ counts={Some(siteView.counts)}
+ online={Some(siteRes.online)}
+ showLocal={showLocal(this.isoData)}
+ />
{this.hasFollows && (
<div className="card border-secondary mb-3">
<div className="card-body">{this.subscribedCommunities()}</div>
this.setState({ trendingCommunities: data.communities });
} else if (op == UserOperation.EditSite) {
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"));
} else if (op == UserOperation.GetPosts) {
let data = wsJsonToRes<GetPostsResponse>(msg, GetPostsResponse);
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView => `${i18n.t("instances")} - ${siteView.site.name}`,
- none: "",
- });
+ return `${i18n.t("instances")} - ${this.state.siteRes.site_view.site.name}`;
}
render() {
description={None}
image={None}
/>
- {this.state.siteRes.site_view.match({
- some: siteView =>
- siteView.site.legal_information.match({
- some: legal => (
- <div
- className="md-div"
- dangerouslySetInnerHTML={mdToHtml(legal)}
- />
- ),
- none: <></>,
- }),
+ {this.state.siteRes.site_view.local_site.legal_information.match({
+ some: legal => (
+ <div className="md-div" dangerouslySetInnerHTML={mdToHtml(legal)} />
+ ),
none: <></>,
})}
</div>
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView => `${i18n.t("login")} - ${siteView.site.name}`,
- none: "",
- });
+ return `${i18n.t("login")} - ${this.state.siteRes.site_view.site.name}`;
}
get isLemmyMl(): boolean {
let data = wsJsonToRes<LoginResponse>(msg, LoginResponse);
this.setState(this.emptyState);
UserService.Instance.login(data);
+ this.props.history.push("/");
+ location.reload();
} else if (op == UserOperation.PasswordReset) {
toast(i18n.t("reset_password_mail_sent"));
} else if (op == UserOperation.GetSite) {
import { Component, linkEvent } from "inferno";
import { Helmet } from "inferno-helmet";
import {
+ GetSiteResponse,
LoginResponse,
Register,
toUndefined,
import { delay, retryWhen, take } from "rxjs/operators";
import { i18n } from "../../i18next";
import { UserService, WebSocketService } from "../../services";
-import { toast, wsClient } from "../../utils";
+import { setIsoData, toast, wsClient } from "../../utils";
import { Spinner } from "../common/icon";
import { SiteForm } from "./site-form";
userForm: Register;
doneRegisteringUser: boolean;
userLoading: boolean;
+ siteRes: GetSiteResponse;
}
export class Setup extends Component<any, State> {
private subscription: Subscription;
+ private isoData = setIsoData(this.context);
private emptyState: State = {
userForm: new Register({
}),
doneRegisteringUser: UserService.Instance.myUserInfo.isSome(),
userLoading: false,
+ siteRes: this.isoData.site_res,
};
constructor(props: any, context: any) {
{!this.state.doneRegisteringUser ? (
this.registerUser()
) : (
- <SiteForm site={None} showLocal />
+ <SiteForm siteRes={this.state.siteRes} showLocal />
)}
</div>
</div>
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView => `${this.titleName(siteView)} - ${siteView.site.name}`,
- none: "",
- });
+ let siteView = this.state.siteRes.site_view;
+ return `${this.titleName(siteView)} - ${siteView.site.name}`;
}
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 {
}
registerForm() {
- return this.state.siteRes.site_view.match({
- some: siteView => (
- <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
- <h5>{this.titleName(siteView)}</h5>
+ let siteView = this.state.siteRes.site_view;
+ return (
+ <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
+ <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>
+ {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>
+ )}
+
+ <div className="form-group row">
+ <label
+ className="col-sm-2 col-form-label"
+ htmlFor="register-username"
+ >
+ {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 className="form-group row">
- <label
- className="col-sm-2 col-form-label"
- htmlFor="register-username"
- >
- {i18n.t("username")}
- </label>
+ <div className="form-group row">
+ <label className="col-sm-2 col-form-label" htmlFor="register-email">
+ {i18n.t("email")}
+ </label>
+ <div className="col-sm-10">
+ <input
+ type="email"
+ id="register-email"
+ className="form-control"
+ placeholder={
+ siteView.local_site.require_email_verification
+ ? i18n.t("required")
+ : i18n.t("optional")
+ }
+ value={toUndefined(this.state.registerForm.email)}
+ autoComplete="email"
+ onInput={linkEvent(this, this.handleRegisterEmailChange)}
+ required={siteView.local_site.require_email_verification}
+ minLength={3}
+ />
+ {!siteView.local_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 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 className="form-group row">
+ <label
+ className="col-sm-2 col-form-label"
+ htmlFor="register-password"
+ >
+ {i18n.t("password")}
+ </label>
+ <div className="col-sm-10">
+ <input
+ type="password"
+ id="register-password"
+ value={this.state.registerForm.password}
+ autoComplete="new-password"
+ onInput={linkEvent(this, this.handleRegisterPasswordChange)}
+ minLength={10}
+ maxLength={60}
+ className="form-control"
+ required
+ />
+ {this.state.registerForm.password && (
+ <div className={this.passwordColorClass}>
+ {i18n.t(this.passwordStrength as I18nKeys)}
+ </div>
+ )}
</div>
+ </div>
- <div className="form-group row">
- <label className="col-sm-2 col-form-label" htmlFor="register-email">
- {i18n.t("email")}
- </label>
- <div className="col-sm-10">
- <input
- type="email"
- id="register-email"
- className="form-control"
- placeholder={
- siteView.site.require_email_verification
- ? i18n.t("required")
- : i18n.t("optional")
- }
- value={toUndefined(this.state.registerForm.email)}
- autoComplete="email"
- onInput={linkEvent(this, this.handleRegisterEmailChange)}
- required={siteView.site.require_email_verification}
- minLength={3}
- />
- {!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 className="form-group row">
+ <label
+ className="col-sm-2 col-form-label"
+ htmlFor="register-verify-password"
+ >
+ {i18n.t("verify_password")}
+ </label>
+ <div className="col-sm-10">
+ <input
+ type="password"
+ id="register-verify-password"
+ value={this.state.registerForm.password_verify}
+ autoComplete="new-password"
+ onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
+ maxLength={60}
+ className="form-control"
+ required
+ />
</div>
+ </div>
- <div className="form-group row">
- <label
- className="col-sm-2 col-form-label"
- htmlFor="register-password"
- >
- {i18n.t("password")}
- </label>
- <div className="col-sm-10">
- <input
- type="password"
- id="register-password"
- value={this.state.registerForm.password}
- autoComplete="new-password"
- onInput={linkEvent(this, this.handleRegisterPasswordChange)}
- minLength={10}
- maxLength={60}
- className="form-control"
- required
- />
- {this.state.registerForm.password && (
- <div className={this.passwordColorClass}>
- {i18n.t(this.passwordStrength as I18nKeys)}
+ {siteView.local_site.require_application && (
+ <>
+ <div className="form-group row">
+ <div className="offset-sm-2 col-sm-10">
+ <div className="mt-2 alert alert-warning" role="alert">
+ <Icon icon="alert-triangle" classes="icon-inline mr-2" />
+ {i18n.t("fill_out_application")}
</div>
- )}
+ {siteView.local_site.application_question.match({
+ some: question => (
+ <div
+ className="md-div"
+ dangerouslySetInnerHTML={mdToHtml(question)}
+ />
+ ),
+ none: <></>,
+ })}
+ </div>
</div>
- </div>
+ <div className="form-group row">
+ <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">
- <label
- className="col-sm-2 col-form-label"
- htmlFor="register-verify-password"
- >
- {i18n.t("verify_password")}
+ <label className="col-sm-2" htmlFor="register-captcha">
+ <span className="mr-2">{i18n.t("enter_code")}</span>
+ <button
+ type="button"
+ className="btn btn-secondary"
+ onClick={linkEvent(this, this.handleRegenCaptcha)}
+ aria-label={i18n.t("captcha")}
+ >
+ <Icon icon="refresh-cw" classes="icon-refresh-cw" />
+ </button>
</label>
- <div className="col-sm-10">
+ {this.showCaptcha()}
+ <div className="col-sm-6">
<input
- type="password"
- id="register-verify-password"
- value={this.state.registerForm.password_verify}
- autoComplete="new-password"
+ type="text"
+ className="form-control"
+ id="register-captcha"
+ value={toUndefined(this.state.registerForm.captcha_answer)}
onInput={linkEvent(
this,
- this.handleRegisterPasswordVerifyChange
+ this.handleRegisterCaptchaAnswerChange
)}
- maxLength={60}
- className="form-control"
required
/>
</div>
</div>
-
- {siteView.site.require_application && (
- <>
- <div className="form-group row">
- <div className="offset-sm-2 col-sm-10">
- <div className="mt-2 alert alert-warning" role="alert">
- <Icon icon="alert-triangle" classes="icon-inline mr-2" />
- {i18n.t("fill_out_application")}
- </div>
- {siteView.site.application_question.match({
- some: question => (
- <div
- className="md-div"
- dangerouslySetInnerHTML={mdToHtml(question)}
- />
- ),
- none: <></>,
- })}
- </div>
- </div>
-
- <div className="form-group row">
+ )}
+ {siteView.local_site.enable_nsfw && (
+ <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="col-sm-2 col-form-label"
- htmlFor="application_answer"
+ className="form-check-label"
+ htmlFor="register-show-nsfw"
>
- {i18n.t("answer")}
+ {i18n.t("show_nsfw")}
</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">
- <label className="col-sm-2" htmlFor="register-captcha">
- <span className="mr-2">{i18n.t("enter_code")}</span>
- <button
- type="button"
- className="btn btn-secondary"
- onClick={linkEvent(this, this.handleRegenCaptcha)}
- aria-label={i18n.t("captcha")}
- >
- <Icon icon="refresh-cw" classes="icon-refresh-cw" />
- </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>
- )}
- {siteView.site.enable_nsfw && (
- <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>
- ),
- none: <></>,
- });
+ )}
+ <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() {
if (data.jwt.isSome()) {
UserService.Instance.login(data);
this.props.history.push("/communities");
+ location.reload();
} else {
if (data.verify_email_sent) {
toast(i18n.t("verify_email_sent"));
import {
CreateSite,
EditSite,
+ GetSiteResponse,
ListingType,
- Site,
toUndefined,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
} from "../../utils";
import { Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
+import { LanguageSelect } from "../common/language-select";
import { ListingTypeSelect } from "../common/listing-type-select";
import { MarkdownTextArea } from "../common/markdown-textarea";
interface SiteFormProps {
- site: Option<Site>; // If a site is given, that means this is an edit
+ siteRes: GetSiteResponse;
showLocal?: boolean;
- onCancel?(): void;
- onEdit?(): void;
}
interface SiteFormState {
export class SiteForm extends Component<SiteFormProps, SiteFormState> {
private emptyState: SiteFormState = {
siteForm: new EditSite({
- enable_downvotes: Some(true),
- open_registration: Some(true),
- enable_nsfw: Some(true),
+ enable_downvotes: None,
+ open_registration: None,
+ enable_nsfw: None,
name: None,
icon: None,
banner: None,
description: None,
community_creation_admin_only: 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,
- hide_modlog_mod_names: Some(true),
}),
loading: false,
themeList: None,
this.handleDefaultPostListingTypeChange =
this.handleDefaultPostListingTypeChange.bind(this);
- if (this.props.site.isSome()) {
- let site = this.props.site.unwrap();
- this.state = {
- ...this.state,
- siteForm: new EditSite({
- name: Some(site.name),
- sidebar: site.sidebar,
- description: site.description,
- enable_downvotes: Some(site.enable_downvotes),
- open_registration: Some(site.open_registration),
- enable_nsfw: Some(site.enable_nsfw),
- community_creation_admin_only: Some(
- site.community_creation_admin_only
- ),
- icon: site.icon,
- banner: site.banner,
- require_email_verification: Some(site.require_email_verification),
- require_application: Some(site.require_application),
- application_question: site.application_question,
- private_instance: Some(site.private_instance),
- default_theme: Some(site.default_theme),
- default_post_listing_type: Some(site.default_post_listing_type),
- legal_information: site.legal_information,
- application_email_admins: Some(site.application_email_admins),
- hide_modlog_mod_names: site.hide_modlog_mod_names,
- auth: undefined,
- }),
- };
- }
+ this.handleDiscussionLanguageChange =
+ this.handleDiscussionLanguageChange.bind(this);
+
+ let site = this.props.siteRes.site_view.site;
+ let ls = this.props.siteRes.site_view.local_site;
+ let lsrl = this.props.siteRes.site_view.local_site_rate_limit;
+ this.state = {
+ ...this.state,
+ siteForm: new EditSite({
+ name: Some(site.name),
+ sidebar: site.sidebar,
+ description: site.description,
+ enable_downvotes: Some(ls.enable_downvotes),
+ open_registration: Some(ls.open_registration),
+ enable_nsfw: Some(ls.enable_nsfw),
+ community_creation_admin_only: Some(ls.community_creation_admin_only),
+ icon: site.icon,
+ banner: site.banner,
+ require_email_verification: Some(ls.require_email_verification),
+ require_application: Some(ls.require_application),
+ application_question: ls.application_question,
+ private_instance: Some(ls.private_instance),
+ default_theme: Some(ls.default_theme),
+ default_post_listing_type: Some(ls.default_post_listing_type),
+ legal_information: ls.legal_information,
+ 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() {
componentDidUpdate() {
if (
!this.state.loading &&
- this.props.site.isNone() &&
+ !this.props.siteRes.site_view.local_site.site_setup &&
(this.state.siteForm.name ||
this.state.siteForm.sidebar ||
this.state.siteForm.application_question ||
}
render() {
+ let siteSetup = this.props.siteRes.site_view.local_site.site_setup;
return (
<>
<Prompt
when={
!this.state.loading &&
- this.props.site.isNone() &&
+ !siteSetup &&
(this.state.siteForm.name ||
this.state.siteForm.sidebar ||
this.state.siteForm.application_question ||
/>
<form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}>
<h5>{`${
- this.props.site.isSome()
+ siteSetup
? capitalizeFirstLetter(i18n.t("save"))
: capitalizeFirstLetter(i18n.t("name"))
} ${i18n.t("your_site")}`}</h5>
</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="col-12">
<button
>
{this.state.loading ? (
<Spinner />
- ) : this.props.site.isSome() ? (
+ ) : siteSetup ? (
capitalizeFirstLetter(i18n.t("save"))
) : (
capitalizeFirstLetter(i18n.t("create"))
)}
</button>
- {this.props.site.isSome() && (
- <button
- type="button"
- className="btn btn-secondary"
- onClick={linkEvent(this, this.handleCancel)}
- >
- {i18n.t("cancel")}
- </button>
- )}
</div>
</div>
</form>
i.setState({ loading: true });
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));
- i.props.onEdit();
} else {
let sForm = i.state.siteForm;
let form = new CreateSite({
application_email_admins: sForm.application_email_admins,
auth: auth().unwrap(),
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));
}
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) {
i.state.siteForm.name = Some(event.target.value);
i.setState(i.state);
i.setState(i.state);
}
- handleCancel(i: SiteForm) {
- i.props.onCancel();
- }
-
handleIconUpload(url: string) {
this.setState(s => ((s.siteForm.icon = Some(url)), 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) {
this.setState(
s => (
);
}
}
+
+function splitToList(commaList: string): Option<string[]> {
+ if (commaList !== "") {
+ let list = commaList.trim().split(",");
+ return Some(list);
+ } else {
+ return Some([]);
+ }
+}
-import { None, Option, Some } from "@sniptt/monads";
+import { None, Option } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
import { PersonViewSafe, Site, SiteAggregates } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { amAdmin, mdToHtml, numToSI } from "../../utils";
+import { mdToHtml, numToSI } from "../../utils";
import { BannerIconHeader } from "../common/banner-icon-header";
import { Icon } from "../common/icon";
import { PersonListing } from "../person/person-listing";
-import { SiteForm } from "./site-form";
interface SiteSidebarProps {
site: Site;
interface SiteSidebarState {
collapsed: boolean;
- showEdit: boolean;
}
export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
private emptyState: SiteSidebarState = {
collapsed: false,
- showEdit: false,
};
constructor(props: any, context: any) {
super(props, context);
this.state = this.emptyState;
- this.handleEditCancel = this.handleEditCancel.bind(this);
- this.handleEditSite = this.handleEditSite.bind(this);
}
render() {
- let site = this.props.site;
return (
<div className="card border-secondary mb-3">
<div className="card-body">
- {!this.state.showEdit ? (
- <div>
- <div className="mb-2">
- {this.siteName()}
- {this.props.admins.isSome() && this.adminButtons()}
- </div>
- {!this.state.collapsed && (
- <>
- <BannerIconHeader banner={site.banner} icon={None} />
- {this.siteInfo()}
- </>
- )}
- </div>
- ) : (
- <SiteForm
- site={Some(site)}
- showLocal={this.props.showLocal}
- onEdit={this.handleEditSite}
- onCancel={this.handleEditCancel}
- />
- )}
+ <div>
+ <div className="mb-2">{this.siteName()}</div>
+ {!this.state.collapsed && (
+ <>
+ <BannerIconHeader banner={this.props.site.banner} icon={None} />
+ {this.siteInfo()}
+ </>
+ )}
+ </div>
</div>
</div>
);
}
siteName() {
- let site = this.props.site;
return (
<h5 className="mb-0 d-inline">
- {site.name}
+ {this.props.site.name}
<button
className="btn btn-sm text-muted"
onClick={linkEvent(this, this.handleCollapseSidebar)}
);
}
- 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) {
return (
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(sidebar)} />
handleCollapseSidebar(i: SiteSidebar) {
i.setState({ collapsed: !i.state.collapsed });
}
-
- handleEditClick(i: SiteSidebar) {
- i.setState({ showEdit: true });
- }
-
- handleEditSite() {
- this.setState({ showEdit: false });
- }
-
- handleEditCancel() {
- this.setState({ showEdit: false });
- }
}
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView => `Modlog - ${siteView.site.name}`,
- none: "",
- });
+ return `Modlog - ${this.state.siteRes.site_view.site.name}`;
}
render() {
</option>
</select>
</div>
- {this.state.siteRes.site_view.match({
- some: site_view =>
- !site_view.site.hide_modlog_mod_names.unwrapOr(false) && (
- <div className="form-group col-sm-6">
- <select
- id="filter-mod"
- className="form-control"
- value={toUndefined(this.state.filter_mod)}
- >
- <option>{i18n.t("filter_by_mod")}</option>
- </select>
- </div>
- ),
- none: <></>,
- })}
+ {!this.state.siteRes.site_view.local_site
+ .hide_modlog_mod_names && (
+ <div className="form-group col-sm-6">
+ <select
+ id="filter-mod"
+ className="form-control"
+ value={toUndefined(this.state.filter_mod)}
+ >
+ <option>{i18n.t("filter_by_mod")}</option>
+ </select>
+ </div>
+ )}
<div className="form-group col-sm-6">
<select
id="filter-user"
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView =>
- UserService.Instance.myUserInfo.match({
- some: mui =>
- `@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${
- siteView.site.name
- }`,
- none: "",
- }),
+ return UserService.Instance.myUserInfo.match({
+ some: mui =>
+ `@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${
+ this.state.siteRes.site_view.site.name
+ }`,
none: "",
});
}
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView => `${i18n.t("password_change")} - ${siteView.site.name}`,
- none: "",
- });
+ return `${i18n.t("password_change")} - ${
+ this.state.siteRes.site_view.site.name
+ }`;
}
render() {
this.setState(this.emptyState);
UserService.Instance.login(data);
this.props.history.push("/");
+ location.reload();
}
}
}
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView =>
- this.state.personRes.match({
- some: res =>
- `@${res.person_view.person.name} - ${siteView.site.name}`,
- none: "",
- }),
+ return this.state.personRes.match({
+ some: res =>
+ `@${res.person_view.person.name} - ${this.state.siteRes.site_view.site.name}`,
none: "",
});
}
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView =>
- UserService.Instance.myUserInfo.match({
- some: mui =>
- `@${mui.local_user_view.person.name} ${i18n.t(
- "registration_applications"
- )} - ${siteView.site.name}`,
- none: "",
- }),
+ return UserService.Instance.myUserInfo.match({
+ some: mui =>
+ `@${mui.local_user_view.person.name} ${i18n.t(
+ "registration_applications"
+ )} - ${this.state.siteRes.site_view.site.name}`,
none: "",
});
}
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView =>
- UserService.Instance.myUserInfo.match({
- some: mui =>
- `@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${
- siteView.site.name
- }`,
- none: "",
- }),
+ return UserService.Instance.myUserInfo.match({
+ some: mui =>
+ `@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${
+ this.state.siteRes.site_view.site.name
+ }`,
none: "",
});
}
} else if (op == UserOperation.SaveUserSettings) {
let data = wsJsonToRes<LoginResponse>(msg, LoginResponse);
UserService.Instance.login(data);
+ location.reload();
this.setState({ saveUserSettingsLoading: false });
toast(i18n.t("saved"));
window.scrollTo(0, 0);
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView => `${i18n.t("verify_email")} - ${siteView.site.name}`,
- none: "",
- });
+ return `${i18n.t("verify_email")} - ${
+ this.state.siteRes.site_view.site.name
+ }`;
}
render() {
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView => `${i18n.t("create_post")} - ${siteView.site.name}`,
- none: "",
- });
+ return `${i18n.t("create_post")} - ${
+ this.state.siteRes.site_view.site.name
+ }`;
}
render() {
get documentTitle(): string {
return this.state.postRes.match({
some: res =>
- this.state.siteRes.site_view.match({
- some: siteView =>
- `${res.post_view.post.name} - ${siteView.site.name}`,
- none: "",
- }),
+ `${res.post_view.post.name} - ${this.state.siteRes.site_view.site.name}`,
none: "",
});
}
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView =>
- this.state.q
- ? `${i18n.t("search")} - ${this.state.q} - ${siteView.site.name}`
- : `${i18n.t("search")} - ${siteView.site.name}`,
- none: "",
- });
+ let siteName = this.state.siteRes.site_view.site.name;
+ return this.state.q
+ ? `${i18n.t("search")} - ${this.state.q} - ${siteName}`
+ : `${i18n.t("search")} - ${siteName}`;
}
render() {
toast(i18n.t("logged_in"));
IsomorphicCookie.save("jwt", jwt, { expires, secure: isHttps });
this.setJwtInfo();
- location.reload();
},
none: void 0,
});
}
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 {
- 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 {
siteRes: GetSiteResponse,
myUserInfo = UserService.Instance.myUserInfo
): boolean {
- let adminOnly = siteRes.site_view
- .map(s => s.site.community_creation_admin_only)
- .unwrapOr(false);
+ let adminOnly = siteRes.site_view.local_site.community_creation_admin_only;
return !adminOnly || amAdmin(myUserInfo);
}
"noUnusedParameters": true,\r
"noImplicitReturns": true,\r
"experimentalDecorators": true,\r
+ "strictNullChecks": false,\r
"noFallthroughCasesInSwitch": true\r
},\r
"include": [\r
dependencies:
invert-kv "^1.0.0"
-lemmy-js-client@0.17.0-rc.46:
- version "0.17.0-rc.46"
- resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.46.tgz#c2820821ca46394fd17d1045e54c00a04b15700c"
- integrity sha512-9HqKKsvToSB397ywXpl0jPa7KIhDaULWel0g35CgmfOkylvuTlpF8UZW20vMXr02Br9qvbRf0hRvi9s5uaji+Q==
+lemmy-js-client@0.17.0-rc.51:
+ version "0.17.0-rc.51"
+ resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.51.tgz#adf554b8837741bc9bb419df090744fc2ef8a1fa"
+ integrity sha512-AGXzQptVrdYim/5YrpAnlqAElZl5aTwqZcwffTrzXs4tL91b/APkdoPLUKASGt/5lRng2CP4cQTbykldZyjQRA==
levn@^0.4.1:
version "0.4.1"