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