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