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