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