]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/site-form.tsx
Adding new site setup fields. (#840)
[lemmy-ui.git] / src / shared / components / home / site-form.tsx
1 import { None, Option, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import { Prompt } from "inferno-router";
4 import {
5   CreateSite,
6   EditSite,
7   GetSiteResponse,
8   ListingType,
9   toUndefined,
10 } from "lemmy-js-client";
11 import { i18n } from "../../i18next";
12 import { WebSocketService } from "../../services";
13 import {
14   auth,
15   capitalizeFirstLetter,
16   fetchThemeList,
17   wsClient,
18 } from "../../utils";
19 import { Spinner } from "../common/icon";
20 import { ImageUploadForm } from "../common/image-upload-form";
21 import { LanguageSelect } from "../common/language-select";
22 import { ListingTypeSelect } from "../common/listing-type-select";
23 import { MarkdownTextArea } from "../common/markdown-textarea";
24
25 interface SiteFormProps {
26   siteRes: GetSiteResponse;
27   showLocal?: boolean;
28 }
29
30 interface SiteFormState {
31   siteForm: EditSite;
32   loading: boolean;
33   themeList: Option<string[]>;
34 }
35
36 export class SiteForm extends Component<SiteFormProps, SiteFormState> {
37   private emptyState: SiteFormState = {
38     siteForm: new EditSite({
39       enable_downvotes: None,
40       open_registration: None,
41       enable_nsfw: None,
42       name: None,
43       icon: None,
44       banner: None,
45       require_email_verification: None,
46       require_application: None,
47       application_question: None,
48       private_instance: None,
49       default_theme: None,
50       sidebar: None,
51       default_post_listing_type: None,
52       legal_information: None,
53       description: None,
54       community_creation_admin_only: None,
55       application_email_admins: None,
56       hide_modlog_mod_names: None,
57       discussion_languages: None,
58       slur_filter_regex: None,
59       actor_name_max_length: None,
60       rate_limit_message: None,
61       rate_limit_message_per_second: None,
62       rate_limit_comment: None,
63       rate_limit_comment_per_second: None,
64       rate_limit_image: None,
65       rate_limit_image_per_second: None,
66       rate_limit_post: None,
67       rate_limit_post_per_second: None,
68       rate_limit_register: None,
69       rate_limit_register_per_second: None,
70       rate_limit_search: None,
71       rate_limit_search_per_second: None,
72       federation_enabled: None,
73       federation_debug: None,
74       federation_worker_count: None,
75       federation_strict_allowlist: None,
76       federation_http_fetch_retry_limit: None,
77       captcha_enabled: None,
78       captcha_difficulty: None,
79       allowed_instances: None,
80       blocked_instances: None,
81       auth: undefined,
82     }),
83     loading: false,
84     themeList: None,
85   };
86
87   constructor(props: any, context: any) {
88     super(props, context);
89
90     this.state = this.emptyState;
91     this.handleSiteSidebarChange = this.handleSiteSidebarChange.bind(this);
92     this.handleSiteLegalInfoChange = this.handleSiteLegalInfoChange.bind(this);
93     this.handleSiteApplicationQuestionChange =
94       this.handleSiteApplicationQuestionChange.bind(this);
95
96     this.handleIconUpload = this.handleIconUpload.bind(this);
97     this.handleIconRemove = this.handleIconRemove.bind(this);
98
99     this.handleBannerUpload = this.handleBannerUpload.bind(this);
100     this.handleBannerRemove = this.handleBannerRemove.bind(this);
101
102     this.handleDefaultPostListingTypeChange =
103       this.handleDefaultPostListingTypeChange.bind(this);
104
105     this.handleDiscussionLanguageChange =
106       this.handleDiscussionLanguageChange.bind(this);
107
108     let site = this.props.siteRes.site_view.site;
109     let ls = this.props.siteRes.site_view.local_site;
110     let lsrl = this.props.siteRes.site_view.local_site_rate_limit;
111     this.state = {
112       ...this.state,
113       siteForm: new EditSite({
114         name: Some(site.name),
115         sidebar: site.sidebar,
116         description: site.description,
117         enable_downvotes: Some(ls.enable_downvotes),
118         open_registration: Some(ls.open_registration),
119         enable_nsfw: Some(ls.enable_nsfw),
120         community_creation_admin_only: Some(ls.community_creation_admin_only),
121         icon: site.icon,
122         banner: site.banner,
123         require_email_verification: Some(ls.require_email_verification),
124         require_application: Some(ls.require_application),
125         application_question: ls.application_question,
126         private_instance: Some(ls.private_instance),
127         default_theme: Some(ls.default_theme),
128         default_post_listing_type: Some(ls.default_post_listing_type),
129         legal_information: ls.legal_information,
130         application_email_admins: Some(ls.application_email_admins),
131         hide_modlog_mod_names: Some(ls.hide_modlog_mod_names),
132         discussion_languages: Some(this.props.siteRes.discussion_languages),
133         slur_filter_regex: ls.slur_filter_regex,
134         actor_name_max_length: Some(ls.actor_name_max_length),
135         rate_limit_message: Some(lsrl.message),
136         rate_limit_message_per_second: Some(lsrl.message_per_second),
137         rate_limit_comment: Some(lsrl.comment),
138         rate_limit_comment_per_second: Some(lsrl.comment_per_second),
139         rate_limit_image: Some(lsrl.image),
140         rate_limit_image_per_second: Some(lsrl.image_per_second),
141         rate_limit_post: Some(lsrl.post),
142         rate_limit_post_per_second: Some(lsrl.post_per_second),
143         rate_limit_register: Some(lsrl.register),
144         rate_limit_register_per_second: Some(lsrl.register_per_second),
145         rate_limit_search: Some(lsrl.search),
146         rate_limit_search_per_second: Some(lsrl.search_per_second),
147         federation_enabled: Some(ls.federation_enabled),
148         federation_debug: Some(ls.federation_debug),
149         federation_worker_count: Some(ls.federation_worker_count),
150         federation_strict_allowlist: Some(ls.federation_strict_allowlist),
151         federation_http_fetch_retry_limit: Some(
152           ls.federation_http_fetch_retry_limit
153         ),
154         captcha_enabled: Some(ls.captcha_enabled),
155         captcha_difficulty: Some(ls.captcha_difficulty),
156         allowed_instances: this.props.siteRes.federated_instances.andThen(
157           f => f.allowed
158         ),
159         blocked_instances: this.props.siteRes.federated_instances.andThen(
160           f => f.blocked
161         ),
162         auth: undefined,
163       }),
164     };
165   }
166
167   async componentDidMount() {
168     this.setState({ themeList: Some(await fetchThemeList()) });
169   }
170
171   // Necessary to stop the loading
172   componentWillReceiveProps() {
173     this.setState({ loading: false });
174   }
175
176   componentDidUpdate() {
177     if (
178       !this.state.loading &&
179       !this.props.siteRes.site_view.local_site.site_setup &&
180       (this.state.siteForm.name ||
181         this.state.siteForm.sidebar ||
182         this.state.siteForm.application_question ||
183         this.state.siteForm.description)
184     ) {
185       window.onbeforeunload = () => true;
186     } else {
187       window.onbeforeunload = undefined;
188     }
189   }
190
191   componentWillUnmount() {
192     window.onbeforeunload = null;
193   }
194
195   render() {
196     let siteSetup = this.props.siteRes.site_view.local_site.site_setup;
197     return (
198       <>
199         <Prompt
200           when={
201             !this.state.loading &&
202             !siteSetup &&
203             (this.state.siteForm.name ||
204               this.state.siteForm.sidebar ||
205               this.state.siteForm.application_question ||
206               this.state.siteForm.description)
207           }
208           message={i18n.t("block_leaving")}
209         />
210         <form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}>
211           <h5>{`${
212             siteSetup
213               ? capitalizeFirstLetter(i18n.t("save"))
214               : capitalizeFirstLetter(i18n.t("name"))
215           } ${i18n.t("your_site")}`}</h5>
216           <div className="form-group row">
217             <label className="col-12 col-form-label" htmlFor="create-site-name">
218               {i18n.t("name")}
219             </label>
220             <div className="col-12">
221               <input
222                 type="text"
223                 id="create-site-name"
224                 className="form-control"
225                 value={toUndefined(this.state.siteForm.name)}
226                 onInput={linkEvent(this, this.handleSiteNameChange)}
227                 required
228                 minLength={3}
229                 maxLength={20}
230               />
231             </div>
232           </div>
233           <div className="form-group">
234             <label>{i18n.t("icon")}</label>
235             <ImageUploadForm
236               uploadTitle={i18n.t("upload_icon")}
237               imageSrc={this.state.siteForm.icon}
238               onUpload={this.handleIconUpload}
239               onRemove={this.handleIconRemove}
240               rounded
241             />
242           </div>
243           <div className="form-group">
244             <label>{i18n.t("banner")}</label>
245             <ImageUploadForm
246               uploadTitle={i18n.t("upload_banner")}
247               imageSrc={this.state.siteForm.banner}
248               onUpload={this.handleBannerUpload}
249               onRemove={this.handleBannerRemove}
250             />
251           </div>
252           <div className="form-group row">
253             <label className="col-12 col-form-label" htmlFor="site-desc">
254               {i18n.t("description")}
255             </label>
256             <div className="col-12">
257               <input
258                 type="text"
259                 className="form-control"
260                 id="site-desc"
261                 value={toUndefined(this.state.siteForm.description)}
262                 onInput={linkEvent(this, this.handleSiteDescChange)}
263                 maxLength={150}
264               />
265             </div>
266           </div>
267           <div className="form-group row">
268             <label className="col-12 col-form-label">{i18n.t("sidebar")}</label>
269             <div className="col-12">
270               <MarkdownTextArea
271                 initialContent={this.state.siteForm.sidebar}
272                 initialLanguageId={None}
273                 placeholder={None}
274                 buttonTitle={None}
275                 maxLength={None}
276                 onContentChange={this.handleSiteSidebarChange}
277                 hideNavigationWarnings
278                 allLanguages={[]}
279               />
280             </div>
281           </div>
282           <div className="form-group row">
283             <label className="col-12 col-form-label">
284               {i18n.t("legal_information")}
285             </label>
286             <div className="col-12">
287               <MarkdownTextArea
288                 initialContent={this.state.siteForm.legal_information}
289                 initialLanguageId={None}
290                 placeholder={None}
291                 buttonTitle={None}
292                 maxLength={None}
293                 onContentChange={this.handleSiteLegalInfoChange}
294                 hideNavigationWarnings
295                 allLanguages={[]}
296               />
297             </div>
298           </div>
299           {this.state.siteForm.require_application.unwrapOr(false) && (
300             <div className="form-group row">
301               <label className="col-12 col-form-label">
302                 {i18n.t("application_questionnaire")}
303               </label>
304               <div className="col-12">
305                 <MarkdownTextArea
306                   initialContent={this.state.siteForm.application_question}
307                   initialLanguageId={None}
308                   placeholder={None}
309                   buttonTitle={None}
310                   maxLength={None}
311                   onContentChange={this.handleSiteApplicationQuestionChange}
312                   hideNavigationWarnings
313                   allLanguages={[]}
314                 />
315               </div>
316             </div>
317           )}
318           <div className="form-group row">
319             <div className="col-12">
320               <div className="form-check">
321                 <input
322                   className="form-check-input"
323                   id="create-site-downvotes"
324                   type="checkbox"
325                   checked={toUndefined(this.state.siteForm.enable_downvotes)}
326                   onChange={linkEvent(
327                     this,
328                     this.handleSiteEnableDownvotesChange
329                   )}
330                 />
331                 <label
332                   className="form-check-label"
333                   htmlFor="create-site-downvotes"
334                 >
335                   {i18n.t("enable_downvotes")}
336                 </label>
337               </div>
338             </div>
339           </div>
340           <div className="form-group row">
341             <div className="col-12">
342               <div className="form-check">
343                 <input
344                   className="form-check-input"
345                   id="create-site-enable-nsfw"
346                   type="checkbox"
347                   checked={toUndefined(this.state.siteForm.enable_nsfw)}
348                   onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
349                 />
350                 <label
351                   className="form-check-label"
352                   htmlFor="create-site-enable-nsfw"
353                 >
354                   {i18n.t("enable_nsfw")}
355                 </label>
356               </div>
357             </div>
358           </div>
359           <div className="form-group row">
360             <div className="col-12">
361               <div className="form-check">
362                 <input
363                   className="form-check-input"
364                   id="create-site-open-registration"
365                   type="checkbox"
366                   checked={toUndefined(this.state.siteForm.open_registration)}
367                   onChange={linkEvent(
368                     this,
369                     this.handleSiteOpenRegistrationChange
370                   )}
371                 />
372                 <label
373                   className="form-check-label"
374                   htmlFor="create-site-open-registration"
375                 >
376                   {i18n.t("open_registration")}
377                 </label>
378               </div>
379             </div>
380           </div>
381           <div className="form-group row">
382             <div className="col-12">
383               <div className="form-check">
384                 <input
385                   className="form-check-input"
386                   id="create-site-community-creation-admin-only"
387                   type="checkbox"
388                   checked={toUndefined(
389                     this.state.siteForm.community_creation_admin_only
390                   )}
391                   onChange={linkEvent(
392                     this,
393                     this.handleSiteCommunityCreationAdminOnly
394                   )}
395                 />
396                 <label
397                   className="form-check-label"
398                   htmlFor="create-site-community-creation-admin-only"
399                 >
400                   {i18n.t("community_creation_admin_only")}
401                 </label>
402               </div>
403             </div>
404           </div>
405           <div className="form-group row">
406             <div className="col-12">
407               <div className="form-check">
408                 <input
409                   className="form-check-input"
410                   id="create-site-require-email-verification"
411                   type="checkbox"
412                   checked={toUndefined(
413                     this.state.siteForm.require_email_verification
414                   )}
415                   onChange={linkEvent(
416                     this,
417                     this.handleSiteRequireEmailVerification
418                   )}
419                 />
420                 <label
421                   className="form-check-label"
422                   htmlFor="create-site-require-email-verification"
423                 >
424                   {i18n.t("require_email_verification")}
425                 </label>
426               </div>
427             </div>
428           </div>
429           <div className="form-group row">
430             <div className="col-12">
431               <div className="form-check">
432                 <input
433                   className="form-check-input"
434                   id="create-site-require-application"
435                   type="checkbox"
436                   checked={toUndefined(this.state.siteForm.require_application)}
437                   onChange={linkEvent(this, this.handleSiteRequireApplication)}
438                 />
439                 <label
440                   className="form-check-label"
441                   htmlFor="create-site-require-application"
442                 >
443                   {i18n.t("require_registration_application")}
444                 </label>
445               </div>
446             </div>
447           </div>
448           <div className="form-group row">
449             <div className="col-12">
450               <div className="form-check">
451                 <input
452                   className="form-check-input"
453                   id="create-site-application-email-admins"
454                   type="checkbox"
455                   checked={toUndefined(
456                     this.state.siteForm.application_email_admins
457                   )}
458                   onChange={linkEvent(
459                     this,
460                     this.handleSiteApplicationEmailAdmins
461                   )}
462                 />
463                 <label
464                   className="form-check-label"
465                   htmlFor="create-site-email-admins"
466                 >
467                   {i18n.t("application_email_admins")}
468                 </label>
469               </div>
470             </div>
471           </div>
472           <div className="form-group row">
473             <div className="col-12">
474               <label
475                 className="form-check-label mr-2"
476                 htmlFor="create-site-default-theme"
477               >
478                 {i18n.t("theme")}
479               </label>
480               <select
481                 id="create-site-default-theme"
482                 value={toUndefined(this.state.siteForm.default_theme)}
483                 onChange={linkEvent(this, this.handleSiteDefaultTheme)}
484                 className="custom-select w-auto"
485               >
486                 <option value="browser">{i18n.t("browser_default")}</option>
487                 {this.state.themeList.unwrapOr([]).map(theme => (
488                   <option key={theme} value={theme}>
489                     {theme}
490                   </option>
491                 ))}
492               </select>
493             </div>
494           </div>
495           {this.props.showLocal && (
496             <form className="form-group row">
497               <label className="col-sm-3">{i18n.t("listing_type")}</label>
498               <div className="col-sm-9">
499                 <ListingTypeSelect
500                   type_={
501                     ListingType[
502                       this.state.siteForm.default_post_listing_type.unwrapOr(
503                         "Local"
504                       )
505                     ]
506                   }
507                   showLocal
508                   showSubscribed={false}
509                   onChange={this.handleDefaultPostListingTypeChange}
510                 />
511               </div>
512             </form>
513           )}
514           <div className="form-group row">
515             <div className="col-12">
516               <div className="form-check">
517                 <input
518                   className="form-check-input"
519                   id="create-site-private-instance"
520                   type="checkbox"
521                   checked={toUndefined(this.state.siteForm.private_instance)}
522                   onChange={linkEvent(this, this.handleSitePrivateInstance)}
523                 />
524                 <label
525                   className="form-check-label"
526                   htmlFor="create-site-private-instance"
527                 >
528                   {i18n.t("private_instance")}
529                 </label>
530               </div>
531             </div>
532           </div>
533           <div className="form-group row">
534             <div className="col-12">
535               <div className="form-check">
536                 <input
537                   className="form-check-input"
538                   id="create-site-hide-modlog-mod-names"
539                   type="checkbox"
540                   checked={toUndefined(
541                     this.state.siteForm.hide_modlog_mod_names
542                   )}
543                   onChange={linkEvent(this, this.handleSiteHideModlogModNames)}
544                 />
545                 <label
546                   className="form-check-label"
547                   htmlFor="create-site-hide-modlog-mod-names"
548                 >
549                   {i18n.t("hide_modlog_mod_names")}
550                 </label>
551               </div>
552             </div>
553           </div>
554           <div className="form-group row">
555             <label
556               className="col-12 col-form-label"
557               htmlFor="create-site-slur-filter-regex"
558             >
559               {i18n.t("slur_filter_regex")}
560             </label>
561             <div className="col-12">
562               <input
563                 type="text"
564                 id="create-site-slur-filter-regex"
565                 placeholder="(word1|word2)"
566                 className="form-control"
567                 value={toUndefined(this.state.siteForm.slur_filter_regex)}
568                 onInput={linkEvent(this, this.handleSiteSlurFilterRegex)}
569                 minLength={3}
570               />
571             </div>
572           </div>
573           <LanguageSelect
574             allLanguages={this.props.siteRes.all_languages}
575             selectedLanguageIds={this.state.siteForm.discussion_languages}
576             multiple={true}
577             onChange={this.handleDiscussionLanguageChange}
578           />
579           <div className="form-group row">
580             <label
581               className="col-12 col-form-label"
582               htmlFor="create-site-actor-name"
583             >
584               {i18n.t("actor_name_max_length")}
585             </label>
586             <div className="col-12">
587               <input
588                 type="number"
589                 id="create-site-actor-name"
590                 className="form-control"
591                 min={5}
592                 value={toUndefined(this.state.siteForm.actor_name_max_length)}
593                 onInput={linkEvent(this, this.handleSiteActorNameMaxLength)}
594               />
595             </div>
596           </div>
597           <div className="form-group row">
598             <div className="col-12">
599               <div className="form-check">
600                 <input
601                   className="form-check-input"
602                   id="create-site-federation-enabled"
603                   type="checkbox"
604                   checked={toUndefined(this.state.siteForm.federation_enabled)}
605                   onChange={linkEvent(this, this.handleSiteFederationEnabled)}
606                 />
607                 <label
608                   className="form-check-label"
609                   htmlFor="create-site-federation-enabled"
610                 >
611                   {i18n.t("federation_enabled")}
612                 </label>
613               </div>
614             </div>
615           </div>
616           {this.state.siteForm.federation_enabled.unwrapOr(false) && (
617             <>
618               <div className="form-group row">
619                 <label
620                   className="col-12 col-form-label"
621                   htmlFor="create-site-allowed-instances"
622                 >
623                   {i18n.t("allowed_instances")}
624                 </label>
625                 <div className="col-12">
626                   <input
627                     type="text"
628                     placeholder="instance1.tld,instance2.tld"
629                     id="create-site-allowed-instances"
630                     className="form-control"
631                     value={this.instancesToString(
632                       this.state.siteForm.allowed_instances
633                     )}
634                     onInput={linkEvent(this, this.handleSiteAllowedInstances)}
635                   />
636                 </div>
637               </div>
638               <div className="form-group row">
639                 <label
640                   className="col-12 col-form-label"
641                   htmlFor="create-site-blocked-instances"
642                 >
643                   {i18n.t("blocked_instances")}
644                 </label>
645                 <div className="col-12">
646                   <input
647                     type="text"
648                     placeholder="instance1.tld,instance2.tld"
649                     id="create-site-blocked-instances"
650                     className="form-control"
651                     value={this.instancesToString(
652                       this.state.siteForm.blocked_instances
653                     )}
654                     onInput={linkEvent(this, this.handleSiteBlockedInstances)}
655                   />
656                 </div>
657               </div>
658               <div className="form-group row">
659                 <div className="col-12">
660                   <div className="form-check">
661                     <input
662                       className="form-check-input"
663                       id="create-site-federation-debug"
664                       type="checkbox"
665                       checked={toUndefined(
666                         this.state.siteForm.federation_debug
667                       )}
668                       onChange={linkEvent(this, this.handleSiteFederationDebug)}
669                     />
670                     <label
671                       className="form-check-label"
672                       htmlFor="create-site-federation-debug"
673                     >
674                       {i18n.t("federation_debug")}
675                     </label>
676                   </div>
677                 </div>
678               </div>
679               <div className="form-group row">
680                 <div className="col-12">
681                   <div className="form-check">
682                     <input
683                       className="form-check-input"
684                       id="create-site-federation-strict-allowlist"
685                       type="checkbox"
686                       checked={toUndefined(
687                         this.state.siteForm.federation_strict_allowlist
688                       )}
689                       onChange={linkEvent(
690                         this,
691                         this.handleSiteFederationStrictAllowList
692                       )}
693                     />
694                     <label
695                       className="form-check-label"
696                       htmlFor="create-site-federation-strict-allowlist"
697                     >
698                       {i18n.t("federation_strict_allowlist")}
699                     </label>
700                   </div>
701                 </div>
702               </div>
703               <div className="form-group row">
704                 <label
705                   className="col-12 col-form-label"
706                   htmlFor="create-site-federation-http-fetch-retry-limit"
707                 >
708                   {i18n.t("federation_http_fetch_retry_limit")}
709                 </label>
710                 <div className="col-12">
711                   <input
712                     type="number"
713                     id="create-site-federation-http-fetch-retry-limit"
714                     className="form-control"
715                     min={0}
716                     value={toUndefined(
717                       this.state.siteForm.federation_http_fetch_retry_limit
718                     )}
719                     onInput={linkEvent(
720                       this,
721                       this.handleSiteFederationHttpFetchRetryLimit
722                     )}
723                   />
724                 </div>
725               </div>
726               <div className="form-group row">
727                 <label
728                   className="col-12 col-form-label"
729                   htmlFor="create-site-federation-worker-count"
730                 >
731                   {i18n.t("federation_worker_count")}
732                 </label>
733                 <div className="col-12">
734                   <input
735                     type="number"
736                     id="create-site-federation-worker-count"
737                     className="form-control"
738                     min={0}
739                     value={toUndefined(
740                       this.state.siteForm.federation_worker_count
741                     )}
742                     onInput={linkEvent(
743                       this,
744                       this.handleSiteFederationWorkerCount
745                     )}
746                   />
747                 </div>
748               </div>
749             </>
750           )}
751           <div className="form-group row">
752             <div className="col-12">
753               <div className="form-check">
754                 <input
755                   className="form-check-input"
756                   id="create-site-captcha-enabled"
757                   type="checkbox"
758                   checked={toUndefined(this.state.siteForm.captcha_enabled)}
759                   onChange={linkEvent(this, this.handleSiteCaptchaEnabled)}
760                 />
761                 <label
762                   className="form-check-label"
763                   htmlFor="create-site-captcha-enabled"
764                 >
765                   {i18n.t("captcha_enabled")}
766                 </label>
767               </div>
768             </div>
769           </div>
770           {this.state.siteForm.captcha_enabled.unwrapOr(false) && (
771             <div className="form-group row">
772               <div className="col-12">
773                 <label
774                   className="form-check-label mr-2"
775                   htmlFor="create-site-captcha-difficulty"
776                 >
777                   {i18n.t("captcha_difficulty")}
778                 </label>
779                 <select
780                   id="create-site-captcha-difficulty"
781                   value={toUndefined(this.state.siteForm.captcha_difficulty)}
782                   onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
783                   className="custom-select w-auto"
784                 >
785                   <option value="easy">{i18n.t("easy")}</option>
786                   <option value="medium">{i18n.t("medium")}</option>
787                   <option value="hard">{i18n.t("hard")}</option>
788                 </select>
789               </div>
790             </div>
791           )}
792           <div className="form-group row">
793             <label
794               className="col-12 col-form-label"
795               htmlFor="create-site-rate-limit-message"
796             >
797               {i18n.t("rate_limit_message")}
798             </label>
799             <div className="col-12">
800               <input
801                 type="number"
802                 id="create-site-rate-limit-message"
803                 className="form-control"
804                 min={0}
805                 value={toUndefined(this.state.siteForm.rate_limit_message)}
806                 onInput={linkEvent(this, this.handleSiteRateLimitMessage)}
807               />
808             </div>
809           </div>
810           <div className="form-group row">
811             <label
812               className="col-12 col-form-label"
813               htmlFor="create-site-rate-limit-message-per-second"
814             >
815               {i18n.t("per_second")}
816             </label>
817             <div className="col-12">
818               <input
819                 type="number"
820                 id="create-site-rate-limit-message-per-second"
821                 className="form-control"
822                 min={0}
823                 value={toUndefined(
824                   this.state.siteForm.rate_limit_message_per_second
825                 )}
826                 onInput={linkEvent(
827                   this,
828                   this.handleSiteRateLimitMessagePerSecond
829                 )}
830               />
831             </div>
832           </div>
833           <div className="form-group row">
834             <label
835               className="col-12 col-form-label"
836               htmlFor="create-site-rate-limit-post"
837             >
838               {i18n.t("rate_limit_post")}
839             </label>
840             <div className="col-12">
841               <input
842                 type="number"
843                 id="create-site-rate-limit-post"
844                 className="form-control"
845                 min={0}
846                 value={toUndefined(this.state.siteForm.rate_limit_post)}
847                 onInput={linkEvent(this, this.handleSiteRateLimitPost)}
848               />
849             </div>
850           </div>
851           <div className="form-group row">
852             <label
853               className="col-12 col-form-label"
854               htmlFor="create-site-rate-limit-post-per-second"
855             >
856               {i18n.t("per_second")}
857             </label>
858             <div className="col-12">
859               <input
860                 type="number"
861                 id="create-site-rate-limit-post-per-second"
862                 className="form-control"
863                 min={0}
864                 value={toUndefined(
865                   this.state.siteForm.rate_limit_post_per_second
866                 )}
867                 onInput={linkEvent(this, this.handleSiteRateLimitPostPerSecond)}
868               />
869             </div>
870           </div>
871           <div className="form-group row">
872             <label
873               className="col-12 col-form-label"
874               htmlFor="create-site-rate-limit-register"
875             >
876               {i18n.t("rate_limit_register")}
877             </label>
878             <div className="col-12">
879               <input
880                 type="number"
881                 id="create-site-rate-limit-register"
882                 className="form-control"
883                 min={0}
884                 value={toUndefined(this.state.siteForm.rate_limit_register)}
885                 onInput={linkEvent(this, this.handleSiteRateLimitRegister)}
886               />
887             </div>
888           </div>
889           <div className="form-group row">
890             <label
891               className="col-12 col-form-label"
892               htmlFor="create-site-rate-limit-register-per-second"
893             >
894               {i18n.t("per_second")}
895             </label>
896             <div className="col-12">
897               <input
898                 type="number"
899                 id="create-site-rate-limit-register-per-second"
900                 className="form-control"
901                 min={0}
902                 value={toUndefined(
903                   this.state.siteForm.rate_limit_register_per_second
904                 )}
905                 onInput={linkEvent(
906                   this,
907                   this.handleSiteRateLimitRegisterPerSecond
908                 )}
909               />
910             </div>
911           </div>
912           <div className="form-group row">
913             <label
914               className="col-12 col-form-label"
915               htmlFor="create-site-rate-limit-image"
916             >
917               {i18n.t("rate_limit_image")}
918             </label>
919             <div className="col-12">
920               <input
921                 type="number"
922                 id="create-site-rate-limit-image"
923                 className="form-control"
924                 min={0}
925                 value={toUndefined(this.state.siteForm.rate_limit_image)}
926                 onInput={linkEvent(this, this.handleSiteRateLimitImage)}
927               />
928             </div>
929           </div>
930           <div className="form-group row">
931             <label
932               className="col-12 col-form-label"
933               htmlFor="create-site-rate-limit-image-per-second"
934             >
935               {i18n.t("per_second")}
936             </label>
937             <div className="col-12">
938               <input
939                 type="number"
940                 id="create-site-rate-limit-image-per-second"
941                 className="form-control"
942                 min={0}
943                 value={toUndefined(
944                   this.state.siteForm.rate_limit_image_per_second
945                 )}
946                 onInput={linkEvent(
947                   this,
948                   this.handleSiteRateLimitImagePerSecond
949                 )}
950               />
951             </div>
952           </div>
953           <div className="form-group row">
954             <label
955               className="col-12 col-form-label"
956               htmlFor="create-site-rate-limit-comment"
957             >
958               {i18n.t("rate_limit_comment")}
959             </label>
960             <div className="col-12">
961               <input
962                 type="number"
963                 id="create-site-rate-limit-comment"
964                 className="form-control"
965                 min={0}
966                 value={toUndefined(this.state.siteForm.rate_limit_comment)}
967                 onInput={linkEvent(this, this.handleSiteRateLimitComment)}
968               />
969             </div>
970           </div>
971           <div className="form-group row">
972             <label
973               className="col-12 col-form-label"
974               htmlFor="create-site-rate-limit-comment-per-second"
975             >
976               {i18n.t("per_second")}
977             </label>
978             <div className="col-12">
979               <input
980                 type="number"
981                 id="create-site-rate-limit-comment-per-second"
982                 className="form-control"
983                 min={0}
984                 value={toUndefined(
985                   this.state.siteForm.rate_limit_comment_per_second
986                 )}
987                 onInput={linkEvent(
988                   this,
989                   this.handleSiteRateLimitCommentPerSecond
990                 )}
991               />
992             </div>
993           </div>
994           <div className="form-group row">
995             <label
996               className="col-12 col-form-label"
997               htmlFor="create-site-rate-limit-search"
998             >
999               {i18n.t("rate_limit_search")}
1000             </label>
1001             <div className="col-12">
1002               <input
1003                 type="number"
1004                 id="create-site-rate-limit-search"
1005                 className="form-control"
1006                 min={0}
1007                 value={toUndefined(this.state.siteForm.rate_limit_search)}
1008                 onInput={linkEvent(this, this.handleSiteRateLimitSearch)}
1009               />
1010             </div>
1011           </div>
1012           <div className="form-group row">
1013             <label
1014               className="col-12 col-form-label"
1015               htmlFor="create-site-rate-limit-search-per-second"
1016             >
1017               {i18n.t("per_second")}
1018             </label>
1019             <div className="col-12">
1020               <input
1021                 type="number"
1022                 id="create-site-rate-limit-search-per-second"
1023                 className="form-control"
1024                 min={0}
1025                 value={toUndefined(
1026                   this.state.siteForm.rate_limit_search_per_second
1027                 )}
1028                 onInput={linkEvent(
1029                   this,
1030                   this.handleSiteRateLimitSearchPerSecond
1031                 )}
1032               />
1033             </div>
1034           </div>
1035           <div className="form-group row">
1036             <div className="col-12">
1037               <button
1038                 type="submit"
1039                 className="btn btn-secondary mr-2"
1040                 disabled={this.state.loading}
1041               >
1042                 {this.state.loading ? (
1043                   <Spinner />
1044                 ) : siteSetup ? (
1045                   capitalizeFirstLetter(i18n.t("save"))
1046                 ) : (
1047                   capitalizeFirstLetter(i18n.t("create"))
1048                 )}
1049               </button>
1050             </div>
1051           </div>
1052         </form>
1053       </>
1054     );
1055   }
1056
1057   handleCreateSiteSubmit(i: SiteForm, event: any) {
1058     event.preventDefault();
1059     i.setState({ loading: true });
1060     i.setState(s => ((s.siteForm.auth = auth().unwrap()), s));
1061
1062     if (i.props.siteRes.site_view.local_site.site_setup) {
1063       WebSocketService.Instance.send(wsClient.editSite(i.state.siteForm));
1064     } else {
1065       let sForm = i.state.siteForm;
1066       let form = new CreateSite({
1067         name: sForm.name.unwrapOr("My site"),
1068         sidebar: sForm.sidebar,
1069         description: sForm.description,
1070         icon: sForm.icon,
1071         banner: sForm.banner,
1072         community_creation_admin_only: sForm.community_creation_admin_only,
1073         enable_nsfw: sForm.enable_nsfw,
1074         enable_downvotes: sForm.enable_downvotes,
1075         require_application: sForm.require_application,
1076         application_question: sForm.application_question,
1077         open_registration: sForm.open_registration,
1078         require_email_verification: sForm.require_email_verification,
1079         private_instance: sForm.private_instance,
1080         default_theme: sForm.default_theme,
1081         default_post_listing_type: sForm.default_post_listing_type,
1082         application_email_admins: sForm.application_email_admins,
1083         auth: auth().unwrap(),
1084         hide_modlog_mod_names: sForm.hide_modlog_mod_names,
1085         legal_information: sForm.legal_information,
1086         slur_filter_regex: sForm.slur_filter_regex,
1087         actor_name_max_length: sForm.actor_name_max_length,
1088         rate_limit_message: sForm.rate_limit_message,
1089         rate_limit_message_per_second: sForm.rate_limit_message_per_second,
1090         rate_limit_comment: sForm.rate_limit_comment,
1091         rate_limit_comment_per_second: sForm.rate_limit_comment_per_second,
1092         rate_limit_image: sForm.rate_limit_image,
1093         rate_limit_image_per_second: sForm.rate_limit_image_per_second,
1094         rate_limit_post: sForm.rate_limit_post,
1095         rate_limit_post_per_second: sForm.rate_limit_post_per_second,
1096         rate_limit_register: sForm.rate_limit_register,
1097         rate_limit_register_per_second: sForm.rate_limit_register_per_second,
1098         rate_limit_search: sForm.rate_limit_search,
1099         rate_limit_search_per_second: sForm.rate_limit_search_per_second,
1100         federation_enabled: sForm.federation_enabled,
1101         federation_debug: sForm.federation_debug,
1102         federation_worker_count: sForm.federation_worker_count,
1103         federation_strict_allowlist: sForm.federation_strict_allowlist,
1104         federation_http_fetch_retry_limit:
1105           sForm.federation_http_fetch_retry_limit,
1106         captcha_enabled: sForm.captcha_enabled,
1107         captcha_difficulty: sForm.captcha_difficulty,
1108         allowed_instances: sForm.allowed_instances,
1109         blocked_instances: sForm.blocked_instances,
1110         discussion_languages: sForm.discussion_languages,
1111       });
1112       WebSocketService.Instance.send(wsClient.createSite(form));
1113     }
1114     i.setState(i.state);
1115   }
1116
1117   instancesToString(opt: Option<string[]>): string {
1118     return opt.map(list => list.join(",")).unwrapOr("");
1119   }
1120
1121   handleSiteAllowedInstances(i: SiteForm, event: any) {
1122     let list = splitToList(event.target.value);
1123     i.setState(s => ((s.siteForm.allowed_instances = list), s));
1124   }
1125
1126   handleSiteBlockedInstances(i: SiteForm, event: any) {
1127     let list = splitToList(event.target.value);
1128     i.setState(s => ((s.siteForm.blocked_instances = list), s));
1129   }
1130
1131   handleSiteNameChange(i: SiteForm, event: any) {
1132     i.state.siteForm.name = Some(event.target.value);
1133     i.setState(i.state);
1134   }
1135
1136   handleSiteSidebarChange(val: string) {
1137     this.setState(s => ((s.siteForm.sidebar = Some(val)), s));
1138   }
1139
1140   handleSiteLegalInfoChange(val: string) {
1141     this.setState(s => ((s.siteForm.legal_information = Some(val)), s));
1142   }
1143
1144   handleSiteApplicationQuestionChange(val: string) {
1145     this.setState(s => ((s.siteForm.application_question = Some(val)), s));
1146   }
1147
1148   handleSiteDescChange(i: SiteForm, event: any) {
1149     i.state.siteForm.description = Some(event.target.value);
1150     i.setState(i.state);
1151   }
1152
1153   handleSiteEnableNsfwChange(i: SiteForm, event: any) {
1154     i.state.siteForm.enable_nsfw = Some(event.target.checked);
1155     i.setState(i.state);
1156   }
1157
1158   handleSiteOpenRegistrationChange(i: SiteForm, event: any) {
1159     i.state.siteForm.open_registration = Some(event.target.checked);
1160     i.setState(i.state);
1161   }
1162
1163   handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) {
1164     i.state.siteForm.community_creation_admin_only = Some(event.target.checked);
1165     i.setState(i.state);
1166   }
1167
1168   handleSiteEnableDownvotesChange(i: SiteForm, event: any) {
1169     i.state.siteForm.enable_downvotes = Some(event.target.checked);
1170     i.setState(i.state);
1171   }
1172
1173   handleSiteRequireApplication(i: SiteForm, event: any) {
1174     i.state.siteForm.require_application = Some(event.target.checked);
1175     i.setState(i.state);
1176   }
1177
1178   handleSiteRequireEmailVerification(i: SiteForm, event: any) {
1179     i.state.siteForm.require_email_verification = Some(event.target.checked);
1180     i.setState(i.state);
1181   }
1182
1183   handleSiteApplicationEmailAdmins(i: SiteForm, event: any) {
1184     i.state.siteForm.application_email_admins = Some(event.target.checked);
1185     i.setState(i.state);
1186   }
1187
1188   handleSitePrivateInstance(i: SiteForm, event: any) {
1189     i.state.siteForm.private_instance = Some(event.target.checked);
1190     i.setState(i.state);
1191   }
1192
1193   handleSiteHideModlogModNames(i: SiteForm, event: any) {
1194     i.state.siteForm.hide_modlog_mod_names = Some(event.target.checked);
1195     i.setState(i.state);
1196   }
1197
1198   handleSiteDefaultTheme(i: SiteForm, event: any) {
1199     i.state.siteForm.default_theme = Some(event.target.value);
1200     i.setState(i.state);
1201   }
1202
1203   handleIconUpload(url: string) {
1204     this.setState(s => ((s.siteForm.icon = Some(url)), s));
1205   }
1206
1207   handleIconRemove() {
1208     this.setState(s => ((s.siteForm.icon = Some("")), s));
1209   }
1210
1211   handleBannerUpload(url: string) {
1212     this.setState(s => ((s.siteForm.banner = Some(url)), s));
1213   }
1214
1215   handleBannerRemove() {
1216     this.setState(s => ((s.siteForm.banner = Some("")), s));
1217   }
1218
1219   handleSiteSlurFilterRegex(i: SiteForm, event: any) {
1220     i.setState(
1221       s => ((s.siteForm.slur_filter_regex = Some(event.target.value)), s)
1222     );
1223   }
1224
1225   handleSiteActorNameMaxLength(i: SiteForm, event: any) {
1226     i.setState(
1227       s => (
1228         (s.siteForm.actor_name_max_length = Some(Number(event.target.value))), s
1229       )
1230     );
1231   }
1232
1233   handleSiteRateLimitMessage(i: SiteForm, event: any) {
1234     i.setState(
1235       s => (
1236         (s.siteForm.rate_limit_message = Some(Number(event.target.value))), s
1237       )
1238     );
1239   }
1240
1241   handleSiteRateLimitMessagePerSecond(i: SiteForm, event: any) {
1242     i.setState(
1243       s => (
1244         (s.siteForm.rate_limit_message_per_second = Some(
1245           Number(event.target.value)
1246         )),
1247         s
1248       )
1249     );
1250   }
1251
1252   handleSiteRateLimitPost(i: SiteForm, event: any) {
1253     i.setState(
1254       s => ((s.siteForm.rate_limit_post = Some(Number(event.target.value))), s)
1255     );
1256   }
1257
1258   handleSiteRateLimitPostPerSecond(i: SiteForm, event: any) {
1259     i.setState(
1260       s => (
1261         (s.siteForm.rate_limit_post_per_second = Some(
1262           Number(event.target.value)
1263         )),
1264         s
1265       )
1266     );
1267   }
1268
1269   handleSiteRateLimitImage(i: SiteForm, event: any) {
1270     i.setState(
1271       s => ((s.siteForm.rate_limit_image = Some(Number(event.target.value))), s)
1272     );
1273   }
1274
1275   handleSiteRateLimitImagePerSecond(i: SiteForm, event: any) {
1276     i.setState(
1277       s => (
1278         (s.siteForm.rate_limit_image_per_second = Some(
1279           Number(event.target.value)
1280         )),
1281         s
1282       )
1283     );
1284   }
1285
1286   handleSiteRateLimitComment(i: SiteForm, event: any) {
1287     i.setState(
1288       s => (
1289         (s.siteForm.rate_limit_comment = Some(Number(event.target.value))), s
1290       )
1291     );
1292   }
1293
1294   handleSiteRateLimitCommentPerSecond(i: SiteForm, event: any) {
1295     i.setState(
1296       s => (
1297         (s.siteForm.rate_limit_comment_per_second = Some(
1298           Number(event.target.value)
1299         )),
1300         s
1301       )
1302     );
1303   }
1304
1305   handleSiteRateLimitSearch(i: SiteForm, event: any) {
1306     i.setState(
1307       s => (
1308         (s.siteForm.rate_limit_search = Some(Number(event.target.value))), s
1309       )
1310     );
1311   }
1312
1313   handleSiteRateLimitSearchPerSecond(i: SiteForm, event: any) {
1314     i.setState(
1315       s => (
1316         (s.siteForm.rate_limit_search_per_second = Some(
1317           Number(event.target.value)
1318         )),
1319         s
1320       )
1321     );
1322   }
1323
1324   handleSiteRateLimitRegister(i: SiteForm, event: any) {
1325     i.setState(
1326       s => (
1327         (s.siteForm.rate_limit_register = Some(Number(event.target.value))), s
1328       )
1329     );
1330   }
1331
1332   handleSiteRateLimitRegisterPerSecond(i: SiteForm, event: any) {
1333     i.setState(
1334       s => (
1335         (s.siteForm.rate_limit_register_per_second = Some(
1336           Number(event.target.value)
1337         )),
1338         s
1339       )
1340     );
1341   }
1342
1343   handleSiteFederationEnabled(i: SiteForm, event: any) {
1344     i.state.siteForm.federation_enabled = Some(event.target.checked);
1345     i.setState(i.state);
1346   }
1347
1348   handleSiteFederationDebug(i: SiteForm, event: any) {
1349     i.state.siteForm.federation_debug = Some(event.target.checked);
1350     i.setState(i.state);
1351   }
1352
1353   handleSiteFederationStrictAllowList(i: SiteForm, event: any) {
1354     i.state.siteForm.federation_strict_allowlist = Some(event.target.checked);
1355     i.setState(i.state);
1356   }
1357
1358   handleSiteFederationWorkerCount(i: SiteForm, event: any) {
1359     i.setState(
1360       s => (
1361         (s.siteForm.federation_worker_count = Some(Number(event.target.value))),
1362         s
1363       )
1364     );
1365   }
1366
1367   handleSiteFederationHttpFetchRetryLimit(i: SiteForm, event: any) {
1368     i.setState(
1369       s => (
1370         (s.siteForm.federation_http_fetch_retry_limit = Some(
1371           Number(event.target.value)
1372         )),
1373         s
1374       )
1375     );
1376   }
1377
1378   handleSiteCaptchaEnabled(i: SiteForm, event: any) {
1379     i.state.siteForm.captcha_enabled = Some(event.target.checked);
1380     i.setState(i.state);
1381   }
1382
1383   handleSiteCaptchaDifficulty(i: SiteForm, event: any) {
1384     i.setState(
1385       s => ((s.siteForm.captcha_difficulty = Some(event.target.value)), s)
1386     );
1387   }
1388
1389   handleDiscussionLanguageChange(val: number[]) {
1390     this.setState(s => ((s.siteForm.discussion_languages = Some(val)), s));
1391   }
1392
1393   handleDefaultPostListingTypeChange(val: ListingType) {
1394     this.setState(
1395       s => (
1396         (s.siteForm.default_post_listing_type = Some(
1397           ListingType[ListingType[val]]
1398         )),
1399         s
1400       )
1401     );
1402   }
1403 }
1404
1405 function splitToList(commaList: string): Option<string[]> {
1406   if (commaList !== "") {
1407     let list = commaList.trim().split(",");
1408     return Some(list);
1409   } else {
1410     return Some([]);
1411   }
1412 }