]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/site-form.tsx
Merge branch 'main' into refactor-server-tidy
[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 {
8   CreateSite,
9   EditSite,
10   GetSiteResponse,
11   Instance,
12   ListingType,
13 } from "lemmy-js-client";
14 import { i18n } from "../../i18next";
15 import {
16   capitalizeFirstLetter,
17   myAuthRequired,
18   validInstanceTLD,
19 } from "../../utils";
20 import { Icon, Spinner } from "../common/icon";
21 import { ImageUploadForm } from "../common/image-upload-form";
22 import { LanguageSelect } from "../common/language-select";
23 import { ListingTypeSelect } from "../common/listing-type-select";
24 import { MarkdownTextArea } from "../common/markdown-textarea";
25 import NavigationPrompt from "../common/navigation-prompt";
26
27 interface SiteFormProps {
28   blockedInstances?: Instance[];
29   allowedInstances?: Instance[];
30   showLocal?: boolean;
31   themeList?: string[];
32   onSaveSite(form: EditSite): void;
33   siteRes: GetSiteResponse;
34   loading: boolean;
35 }
36
37 interface SiteFormState {
38   siteForm: EditSite;
39   instance_select: {
40     allowed_instances: string;
41     blocked_instances: string;
42   };
43   submitted: boolean;
44 }
45
46 type InstanceKey = "allowed_instances" | "blocked_instances";
47
48 export class SiteForm extends Component<SiteFormProps, SiteFormState> {
49   state: SiteFormState = {
50     siteForm: this.initSiteForm(),
51     instance_select: {
52       allowed_instances: "",
53       blocked_instances: "",
54     },
55     submitted: false,
56   };
57
58   initSiteForm(): EditSite {
59     const site = this.props.siteRes.site_view.site;
60     const ls = this.props.siteRes.site_view.local_site;
61     return {
62       name: site.name,
63       sidebar: site.sidebar,
64       description: site.description,
65       enable_downvotes: ls.enable_downvotes,
66       registration_mode: ls.registration_mode,
67       enable_nsfw: ls.enable_nsfw,
68       community_creation_admin_only: ls.community_creation_admin_only,
69       icon: site.icon,
70       banner: site.banner,
71       require_email_verification: ls.require_email_verification,
72       application_question: ls.application_question,
73       private_instance: ls.private_instance,
74       default_theme: ls.default_theme,
75       default_post_listing_type: ls.default_post_listing_type,
76       legal_information: ls.legal_information,
77       application_email_admins: ls.application_email_admins,
78       reports_email_admins: ls.reports_email_admins,
79       hide_modlog_mod_names: ls.hide_modlog_mod_names,
80       discussion_languages: this.props.siteRes.discussion_languages,
81       slur_filter_regex: ls.slur_filter_regex,
82       actor_name_max_length: ls.actor_name_max_length,
83       federation_enabled: ls.federation_enabled,
84       federation_debug: ls.federation_debug,
85       federation_worker_count: ls.federation_worker_count,
86       captcha_enabled: ls.captcha_enabled,
87       captcha_difficulty: ls.captcha_difficulty,
88       allowed_instances: this.props.allowedInstances?.map(i => i.domain),
89       blocked_instances: this.props.blockedInstances?.map(i => i.domain),
90       auth: "TODO",
91     };
92   }
93
94   constructor(props: any, context: any) {
95     super(props, context);
96
97     this.handleSiteSidebarChange = this.handleSiteSidebarChange.bind(this);
98     this.handleSiteLegalInfoChange = this.handleSiteLegalInfoChange.bind(this);
99     this.handleSiteApplicationQuestionChange =
100       this.handleSiteApplicationQuestionChange.bind(this);
101
102     this.handleIconUpload = this.handleIconUpload.bind(this);
103     this.handleIconRemove = this.handleIconRemove.bind(this);
104
105     this.handleBannerUpload = this.handleBannerUpload.bind(this);
106     this.handleBannerRemove = this.handleBannerRemove.bind(this);
107
108     this.handleDefaultPostListingTypeChange =
109       this.handleDefaultPostListingTypeChange.bind(this);
110
111     this.handleDiscussionLanguageChange =
112       this.handleDiscussionLanguageChange.bind(this);
113
114     this.handleAddInstance = this.handleAddInstance.bind(this);
115     this.handleRemoveInstance = this.handleRemoveInstance.bind(this);
116
117     this.handleInstanceEnterPress = this.handleInstanceEnterPress.bind(this);
118     this.handleInstanceTextChange = this.handleInstanceTextChange.bind(this);
119   }
120
121   render() {
122     const siteSetup = this.props.siteRes.site_view.local_site.site_setup;
123     return (
124       <form onSubmit={linkEvent(this, this.handleSaveSiteSubmit)}>
125         <NavigationPrompt
126           when={
127             !this.props.loading &&
128             !siteSetup &&
129             !!(
130               this.state.siteForm.name ||
131               this.state.siteForm.sidebar ||
132               this.state.siteForm.application_question ||
133               this.state.siteForm.description
134             ) &&
135             !this.state.submitted
136           }
137         />
138         <h5>{`${
139           siteSetup
140             ? capitalizeFirstLetter(i18n.t("edit"))
141             : capitalizeFirstLetter(i18n.t("setup"))
142         } ${i18n.t("your_site")}`}</h5>
143         <div className="form-group row">
144           <label className="col-12 col-form-label" htmlFor="create-site-name">
145             {i18n.t("name")}
146           </label>
147           <div className="col-12">
148             <input
149               type="text"
150               id="create-site-name"
151               className="form-control"
152               value={this.state.siteForm.name}
153               onInput={linkEvent(this, this.handleSiteNameChange)}
154               required
155               minLength={3}
156               maxLength={20}
157             />
158           </div>
159         </div>
160         <div className="form-group">
161           <label className="mr-2">{i18n.t("icon")}</label>
162           <ImageUploadForm
163             uploadTitle={i18n.t("upload_icon")}
164             imageSrc={this.state.siteForm.icon}
165             onUpload={this.handleIconUpload}
166             onRemove={this.handleIconRemove}
167             rounded
168           />
169         </div>
170         <div className="form-group">
171           <label className="mr-2">{i18n.t("banner")}</label>
172           <ImageUploadForm
173             uploadTitle={i18n.t("upload_banner")}
174             imageSrc={this.state.siteForm.banner}
175             onUpload={this.handleBannerUpload}
176             onRemove={this.handleBannerRemove}
177           />
178         </div>
179         <div className="form-group row">
180           <label className="col-12 col-form-label" htmlFor="site-desc">
181             {i18n.t("description")}
182           </label>
183           <div className="col-12">
184             <input
185               type="text"
186               className="form-control"
187               id="site-desc"
188               value={this.state.siteForm.description}
189               onInput={linkEvent(this, this.handleSiteDescChange)}
190               maxLength={150}
191             />
192           </div>
193         </div>
194         <div className="form-group row">
195           <label className="col-12 col-form-label">{i18n.t("sidebar")}</label>
196           <div className="col-12">
197             <MarkdownTextArea
198               initialContent={this.state.siteForm.sidebar}
199               onContentChange={this.handleSiteSidebarChange}
200               hideNavigationWarnings
201               allLanguages={[]}
202               siteLanguages={[]}
203             />
204           </div>
205         </div>
206         <div className="form-group row">
207           <label className="col-12 col-form-label">
208             {i18n.t("legal_information")}
209           </label>
210           <div className="col-12">
211             <MarkdownTextArea
212               initialContent={this.state.siteForm.legal_information}
213               onContentChange={this.handleSiteLegalInfoChange}
214               hideNavigationWarnings
215               allLanguages={[]}
216               siteLanguages={[]}
217             />
218           </div>
219         </div>
220         <div className="form-group row">
221           <div className="col-12">
222             <div className="form-check">
223               <input
224                 className="form-check-input"
225                 id="create-site-downvotes"
226                 type="checkbox"
227                 checked={this.state.siteForm.enable_downvotes}
228                 onChange={linkEvent(this, this.handleSiteEnableDownvotesChange)}
229               />
230               <label
231                 className="form-check-label"
232                 htmlFor="create-site-downvotes"
233               >
234                 {i18n.t("enable_downvotes")}
235               </label>
236             </div>
237           </div>
238         </div>
239         <div className="form-group row">
240           <div className="col-12">
241             <div className="form-check">
242               <input
243                 className="form-check-input"
244                 id="create-site-enable-nsfw"
245                 type="checkbox"
246                 checked={this.state.siteForm.enable_nsfw}
247                 onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
248               />
249               <label
250                 className="form-check-label"
251                 htmlFor="create-site-enable-nsfw"
252               >
253                 {i18n.t("enable_nsfw")}
254               </label>
255             </div>
256           </div>
257         </div>
258         <div className="form-group row">
259           <div className="col-12">
260             <label
261               className="form-check-label mr-2"
262               htmlFor="create-site-registration-mode"
263             >
264               {i18n.t("registration_mode")}
265             </label>
266             <select
267               id="create-site-registration-mode"
268               value={this.state.siteForm.registration_mode}
269               onChange={linkEvent(this, this.handleSiteRegistrationModeChange)}
270               className="custom-select w-auto"
271             >
272               <option value={"RequireApplication"}>
273                 {i18n.t("require_registration_application")}
274               </option>
275               <option value={"Open"}>{i18n.t("open_registration")}</option>
276               <option value={"Closed"}>{i18n.t("close_registration")}</option>
277             </select>
278           </div>
279         </div>
280         {this.state.siteForm.registration_mode == "RequireApplication" && (
281           <div className="form-group row">
282             <label className="col-12 col-form-label">
283               {i18n.t("application_questionnaire")}
284             </label>
285             <div className="col-12">
286               <MarkdownTextArea
287                 initialContent={this.state.siteForm.application_question}
288                 onContentChange={this.handleSiteApplicationQuestionChange}
289                 hideNavigationWarnings
290                 allLanguages={[]}
291                 siteLanguages={[]}
292               />
293             </div>
294           </div>
295         )}
296         <div className="form-group row">
297           <div className="col-12">
298             <div className="form-check">
299               <input
300                 className="form-check-input"
301                 id="create-site-community-creation-admin-only"
302                 type="checkbox"
303                 checked={this.state.siteForm.community_creation_admin_only}
304                 onChange={linkEvent(
305                   this,
306                   this.handleSiteCommunityCreationAdminOnly
307                 )}
308               />
309               <label
310                 className="form-check-label"
311                 htmlFor="create-site-community-creation-admin-only"
312               >
313                 {i18n.t("community_creation_admin_only")}
314               </label>
315             </div>
316           </div>
317         </div>
318         <div className="form-group row">
319           <div className="col-12">
320             <div className="form-check">
321               <input
322                 className="form-check-input"
323                 id="create-site-require-email-verification"
324                 type="checkbox"
325                 checked={this.state.siteForm.require_email_verification}
326                 onChange={linkEvent(
327                   this,
328                   this.handleSiteRequireEmailVerification
329                 )}
330               />
331               <label
332                 className="form-check-label"
333                 htmlFor="create-site-require-email-verification"
334               >
335                 {i18n.t("require_email_verification")}
336               </label>
337             </div>
338           </div>
339         </div>
340         <div className="form-group row">
341           <div className="col-12">
342             <div className="form-check">
343               <input
344                 className="form-check-input"
345                 id="create-site-application-email-admins"
346                 type="checkbox"
347                 checked={this.state.siteForm.application_email_admins}
348                 onChange={linkEvent(
349                   this,
350                   this.handleSiteApplicationEmailAdmins
351                 )}
352               />
353               <label
354                 className="form-check-label"
355                 htmlFor="create-site-email-admins"
356               >
357                 {i18n.t("application_email_admins")}
358               </label>
359             </div>
360           </div>
361         </div>
362         <div className="form-group row">
363           <div className="col-12">
364             <div className="form-check">
365               <input
366                 className="form-check-input"
367                 id="create-site-reports-email-admins"
368                 type="checkbox"
369                 checked={this.state.siteForm.reports_email_admins}
370                 onChange={linkEvent(this, this.handleSiteReportsEmailAdmins)}
371               />
372               <label
373                 className="form-check-label"
374                 htmlFor="create-site-reports-email-admins"
375               >
376                 {i18n.t("reports_email_admins")}
377               </label>
378             </div>
379           </div>
380         </div>
381         <div className="form-group row">
382           <div className="col-12">
383             <label
384               className="form-check-label mr-2"
385               htmlFor="create-site-default-theme"
386             >
387               {i18n.t("theme")}
388             </label>
389             <select
390               id="create-site-default-theme"
391               value={this.state.siteForm.default_theme}
392               onChange={linkEvent(this, this.handleSiteDefaultTheme)}
393               className="custom-select w-auto"
394             >
395               <option value="browser">{i18n.t("browser_default")}</option>
396               {this.props.themeList?.map(theme => (
397                 <option key={theme} value={theme}>
398                   {theme}
399                 </option>
400               ))}
401             </select>
402           </div>
403         </div>
404         {this.props.showLocal && (
405           <form className="form-group row">
406             <label className="col-sm-3">{i18n.t("listing_type")}</label>
407             <div className="col-sm-9">
408               <ListingTypeSelect
409                 type_={this.state.siteForm.default_post_listing_type ?? "Local"}
410                 showLocal
411                 showSubscribed={false}
412                 onChange={this.handleDefaultPostListingTypeChange}
413               />
414             </div>
415           </form>
416         )}
417         <div className="form-group row">
418           <div className="col-12">
419             <div className="form-check">
420               <input
421                 className="form-check-input"
422                 id="create-site-private-instance"
423                 type="checkbox"
424                 checked={this.state.siteForm.private_instance}
425                 onChange={linkEvent(this, this.handleSitePrivateInstance)}
426               />
427               <label
428                 className="form-check-label"
429                 htmlFor="create-site-private-instance"
430               >
431                 {i18n.t("private_instance")}
432               </label>
433             </div>
434           </div>
435         </div>
436         <div className="form-group row">
437           <div className="col-12">
438             <div className="form-check">
439               <input
440                 className="form-check-input"
441                 id="create-site-hide-modlog-mod-names"
442                 type="checkbox"
443                 checked={this.state.siteForm.hide_modlog_mod_names}
444                 onChange={linkEvent(this, this.handleSiteHideModlogModNames)}
445               />
446               <label
447                 className="form-check-label"
448                 htmlFor="create-site-hide-modlog-mod-names"
449               >
450                 {i18n.t("hide_modlog_mod_names")}
451               </label>
452             </div>
453           </div>
454         </div>
455         <div className="form-group row">
456           <label
457             className="col-12 col-form-label"
458             htmlFor="create-site-slur-filter-regex"
459           >
460             {i18n.t("slur_filter_regex")}
461           </label>
462           <div className="col-12">
463             <input
464               type="text"
465               id="create-site-slur-filter-regex"
466               placeholder="(word1|word2)"
467               className="form-control"
468               value={this.state.siteForm.slur_filter_regex}
469               onInput={linkEvent(this, this.handleSiteSlurFilterRegex)}
470               minLength={3}
471             />
472           </div>
473         </div>
474         <LanguageSelect
475           allLanguages={this.props.siteRes.all_languages}
476           siteLanguages={this.props.siteRes.discussion_languages}
477           selectedLanguageIds={this.state.siteForm.discussion_languages}
478           multiple={true}
479           onChange={this.handleDiscussionLanguageChange}
480           showAll
481         />
482         <div className="form-group row">
483           <label
484             className="col-12 col-form-label"
485             htmlFor="create-site-actor-name"
486           >
487             {i18n.t("actor_name_max_length")}
488           </label>
489           <div className="col-12">
490             <input
491               type="number"
492               id="create-site-actor-name"
493               className="form-control"
494               min={5}
495               value={this.state.siteForm.actor_name_max_length}
496               onInput={linkEvent(this, this.handleSiteActorNameMaxLength)}
497             />
498           </div>
499         </div>
500         <div className="form-group row">
501           <div className="col-12">
502             <div className="form-check">
503               <input
504                 className="form-check-input"
505                 id="create-site-federation-enabled"
506                 type="checkbox"
507                 checked={this.state.siteForm.federation_enabled}
508                 onChange={linkEvent(this, this.handleSiteFederationEnabled)}
509               />
510               <label
511                 className="form-check-label"
512                 htmlFor="create-site-federation-enabled"
513               >
514                 {i18n.t("federation_enabled")}
515               </label>
516             </div>
517           </div>
518         </div>
519         {this.state.siteForm.federation_enabled && (
520           <>
521             <div className="form-group row">
522               {this.federatedInstanceSelect("allowed_instances")}
523               {this.federatedInstanceSelect("blocked_instances")}
524             </div>
525             <div className="form-group row">
526               <div className="col-12">
527                 <div className="form-check">
528                   <input
529                     className="form-check-input"
530                     id="create-site-federation-debug"
531                     type="checkbox"
532                     checked={this.state.siteForm.federation_debug}
533                     onChange={linkEvent(this, this.handleSiteFederationDebug)}
534                   />
535                   <label
536                     className="form-check-label"
537                     htmlFor="create-site-federation-debug"
538                   >
539                     {i18n.t("federation_debug")}
540                   </label>
541                 </div>
542               </div>
543             </div>
544             <div className="form-group row">
545               <label
546                 className="col-12 col-form-label"
547                 htmlFor="create-site-federation-worker-count"
548               >
549                 {i18n.t("federation_worker_count")}
550               </label>
551               <div className="col-12">
552                 <input
553                   type="number"
554                   id="create-site-federation-worker-count"
555                   className="form-control"
556                   min={0}
557                   value={this.state.siteForm.federation_worker_count}
558                   onInput={linkEvent(
559                     this,
560                     this.handleSiteFederationWorkerCount
561                   )}
562                 />
563               </div>
564             </div>
565           </>
566         )}
567         <div className="form-group row">
568           <div className="col-12">
569             <div className="form-check">
570               <input
571                 className="form-check-input"
572                 id="create-site-captcha-enabled"
573                 type="checkbox"
574                 checked={this.state.siteForm.captcha_enabled}
575                 onChange={linkEvent(this, this.handleSiteCaptchaEnabled)}
576               />
577               <label
578                 className="form-check-label"
579                 htmlFor="create-site-captcha-enabled"
580               >
581                 {i18n.t("captcha_enabled")}
582               </label>
583             </div>
584           </div>
585         </div>
586         {this.state.siteForm.captcha_enabled && (
587           <div className="form-group row">
588             <div className="col-12">
589               <label
590                 className="form-check-label mr-2"
591                 htmlFor="create-site-captcha-difficulty"
592               >
593                 {i18n.t("captcha_difficulty")}
594               </label>
595               <select
596                 id="create-site-captcha-difficulty"
597                 value={this.state.siteForm.captcha_difficulty}
598                 onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
599                 className="custom-select w-auto"
600               >
601                 <option value="easy">{i18n.t("easy")}</option>
602                 <option value="medium">{i18n.t("medium")}</option>
603                 <option value="hard">{i18n.t("hard")}</option>
604               </select>
605             </div>
606           </div>
607         )}
608         <div className="form-group row">
609           <div className="col-12">
610             <button
611               type="submit"
612               className="btn btn-secondary mr-2"
613               disabled={this.props.loading}
614             >
615               {this.props.loading ? (
616                 <Spinner />
617               ) : siteSetup ? (
618                 capitalizeFirstLetter(i18n.t("save"))
619               ) : (
620                 capitalizeFirstLetter(i18n.t("create"))
621               )}
622             </button>
623           </div>
624         </div>
625       </form>
626     );
627   }
628
629   federatedInstanceSelect(key: InstanceKey) {
630     const id = `create_site_${key}`;
631     const value = this.state.instance_select[key];
632     const selectedInstances = this.state.siteForm[key];
633     return (
634       <div className="col-12 col-md-6">
635         <label className="col-form-label" htmlFor={id}>
636           {i18n.t(key)}
637         </label>
638         <div className="d-flex justify-content-between align-items-center">
639           <input
640             type="text"
641             placeholder="instance.tld"
642             id={id}
643             className="form-control"
644             value={value}
645             onInput={linkEvent(key, this.handleInstanceTextChange)}
646             onKeyUp={linkEvent(key, this.handleInstanceEnterPress)}
647           />
648           <button
649             type="button"
650             className="btn btn-sm bg-success ml-2"
651             onClick={linkEvent(key, this.handleAddInstance)}
652             style={"width: 2rem; height: 2rem;"}
653             tabIndex={
654               -1 /* Making this untabble because handling enter key in text input makes keyboard support for this button redundant */
655             }
656           >
657             <Icon
658               icon="add"
659               classes="icon-inline text-light m-auto d-block position-static"
660             />
661           </button>
662         </div>
663         {selectedInstances && selectedInstances.length > 0 && (
664           <ul className="mt-3 list-unstyled w-100 d-flex flex-column justify-content-around align-items-center">
665             {selectedInstances.map(instance => (
666               <li
667                 key={instance}
668                 className="my-1 w-100 w-md-75 d-flex align-items-center justify-content-between"
669               >
670                 <label className="d-block m-0 w-100 " htmlFor={instance}>
671                   <strong>{instance}</strong>
672                 </label>
673                 <button
674                   id={instance}
675                   type="button"
676                   style={"width: 2rem; height: 2rem;"}
677                   className="btn btn-sm bg-danger"
678                   onClick={linkEvent(
679                     { key, instance },
680                     this.handleRemoveInstance
681                   )}
682                 >
683                   <Icon
684                     icon="x"
685                     classes="icon-inline text-light m-auto d-block position-static"
686                   />
687                 </button>
688               </li>
689             ))}
690           </ul>
691         )}
692       </div>
693     );
694   }
695
696   handleInstanceTextChange(type: InstanceKey, event: any) {
697     this.setState(s => ({
698       ...s,
699       instance_select: {
700         ...s.instance_select,
701         [type]: event.target.value,
702       },
703     }));
704   }
705
706   handleInstanceEnterPress(
707     key: InstanceKey,
708     event: InfernoKeyboardEvent<HTMLInputElement>
709   ) {
710     if (event.code.toLowerCase() === "enter") {
711       event.preventDefault();
712
713       this.handleAddInstance(key);
714     }
715   }
716
717   handleSaveSiteSubmit(i: SiteForm, event: any) {
718     event.preventDefault();
719     const auth = myAuthRequired();
720     i.setState(s => ((s.siteForm.auth = auth), s));
721     i.setState({ submitted: true });
722
723     const stateSiteForm = i.state.siteForm;
724
725     let form: EditSite | CreateSite;
726
727     if (i.props.siteRes.site_view.local_site.site_setup) {
728       form = stateSiteForm;
729     } else {
730       form = {
731         name: stateSiteForm.name ?? "My site",
732         sidebar: stateSiteForm.sidebar,
733         description: stateSiteForm.description,
734         icon: stateSiteForm.icon,
735         banner: stateSiteForm.banner,
736         community_creation_admin_only:
737           stateSiteForm.community_creation_admin_only,
738         enable_nsfw: stateSiteForm.enable_nsfw,
739         enable_downvotes: stateSiteForm.enable_downvotes,
740         application_question: stateSiteForm.application_question,
741         registration_mode: stateSiteForm.registration_mode,
742         require_email_verification: stateSiteForm.require_email_verification,
743         private_instance: stateSiteForm.private_instance,
744         default_theme: stateSiteForm.default_theme,
745         default_post_listing_type: stateSiteForm.default_post_listing_type,
746         application_email_admins: stateSiteForm.application_email_admins,
747         hide_modlog_mod_names: stateSiteForm.hide_modlog_mod_names,
748         legal_information: stateSiteForm.legal_information,
749         slur_filter_regex: stateSiteForm.slur_filter_regex,
750         actor_name_max_length: stateSiteForm.actor_name_max_length,
751         rate_limit_message: stateSiteForm.rate_limit_message,
752         rate_limit_message_per_second:
753           stateSiteForm.rate_limit_message_per_second,
754         rate_limit_comment: stateSiteForm.rate_limit_comment,
755         rate_limit_comment_per_second:
756           stateSiteForm.rate_limit_comment_per_second,
757         rate_limit_image: stateSiteForm.rate_limit_image,
758         rate_limit_image_per_second: stateSiteForm.rate_limit_image_per_second,
759         rate_limit_post: stateSiteForm.rate_limit_post,
760         rate_limit_post_per_second: stateSiteForm.rate_limit_post_per_second,
761         rate_limit_register: stateSiteForm.rate_limit_register,
762         rate_limit_register_per_second:
763           stateSiteForm.rate_limit_register_per_second,
764         rate_limit_search: stateSiteForm.rate_limit_search,
765         rate_limit_search_per_second:
766           stateSiteForm.rate_limit_search_per_second,
767         federation_enabled: stateSiteForm.federation_enabled,
768         federation_debug: stateSiteForm.federation_debug,
769         federation_worker_count: stateSiteForm.federation_worker_count,
770         captcha_enabled: stateSiteForm.captcha_enabled,
771         captcha_difficulty: stateSiteForm.captcha_difficulty,
772         allowed_instances: stateSiteForm.allowed_instances,
773         blocked_instances: stateSiteForm.blocked_instances,
774         discussion_languages: stateSiteForm.discussion_languages,
775         auth,
776       };
777     }
778
779     i.props.onSaveSite(form);
780   }
781
782   handleAddInstance(key: InstanceKey) {
783     const instance = this.state.instance_select[key].trim();
784
785     if (!validInstanceTLD(instance)) {
786       return;
787     }
788
789     if (!this.state.siteForm[key]?.includes(instance)) {
790       this.setState(s => ({
791         ...s,
792         siteForm: {
793           ...s.siteForm,
794           [key]: [...(s.siteForm[key] ?? []), instance],
795         },
796         instance_select: {
797           ...s.instance_select,
798           [key]: "",
799         },
800       }));
801
802       const oppositeKey: InstanceKey =
803         key === "allowed_instances" ? "blocked_instances" : "allowed_instances";
804       if (this.state.siteForm[oppositeKey]?.includes(instance)) {
805         this.handleRemoveInstance({ key: oppositeKey, instance });
806       }
807     }
808   }
809
810   handleRemoveInstance({
811     key,
812     instance,
813   }: {
814     key: InstanceKey;
815     instance: string;
816   }) {
817     this.setState(s => ({
818       ...s,
819       siteForm: {
820         ...s.siteForm,
821         [key]: s.siteForm[key]?.filter(i => i !== instance),
822       },
823     }));
824   }
825
826   handleSiteNameChange(i: SiteForm, event: any) {
827     i.state.siteForm.name = event.target.value;
828     i.setState(i.state);
829   }
830
831   handleSiteSidebarChange(val: string) {
832     this.setState(s => ((s.siteForm.sidebar = val), s));
833   }
834
835   handleSiteLegalInfoChange(val: string) {
836     this.setState(s => ((s.siteForm.legal_information = val), s));
837   }
838
839   handleTaglineChange(i: SiteForm, index: number, val: string) {
840     const taglines = i.state.siteForm.taglines;
841     if (taglines) {
842       taglines[index] = val;
843       i.setState(i.state);
844     }
845   }
846
847   handleDeleteTaglineClick(
848     i: SiteForm,
849     index: number,
850     event: InfernoMouseEvent<HTMLButtonElement>
851   ) {
852     event.preventDefault();
853     const taglines = i.state.siteForm.taglines;
854     if (taglines) {
855       taglines.splice(index, 1);
856       i.state.siteForm.taglines = undefined;
857       i.setState(i.state);
858       i.state.siteForm.taglines = taglines;
859       i.setState(i.state);
860     }
861   }
862
863   handleAddTaglineClick(
864     i: SiteForm,
865     event: InfernoMouseEvent<HTMLButtonElement>
866   ) {
867     event.preventDefault();
868     if (!i.state.siteForm.taglines) {
869       i.state.siteForm.taglines = [];
870     }
871     i.state.siteForm.taglines.push("");
872     i.setState(i.state);
873   }
874
875   handleSiteApplicationQuestionChange(val: string) {
876     this.setState(s => ((s.siteForm.application_question = val), s));
877   }
878
879   handleSiteDescChange(i: SiteForm, event: any) {
880     i.state.siteForm.description = event.target.value;
881     i.setState(i.state);
882   }
883
884   handleSiteEnableNsfwChange(i: SiteForm, event: any) {
885     i.state.siteForm.enable_nsfw = event.target.checked;
886     i.setState(i.state);
887   }
888
889   handleSiteRegistrationModeChange(i: SiteForm, event: any) {
890     i.state.siteForm.registration_mode = event.target.value;
891     i.setState(i.state);
892   }
893
894   handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) {
895     i.state.siteForm.community_creation_admin_only = event.target.checked;
896     i.setState(i.state);
897   }
898
899   handleSiteEnableDownvotesChange(i: SiteForm, event: any) {
900     i.state.siteForm.enable_downvotes = event.target.checked;
901     i.setState(i.state);
902   }
903
904   handleSiteRequireEmailVerification(i: SiteForm, event: any) {
905     i.state.siteForm.require_email_verification = event.target.checked;
906     i.setState(i.state);
907   }
908
909   handleSiteApplicationEmailAdmins(i: SiteForm, event: any) {
910     i.state.siteForm.application_email_admins = event.target.checked;
911     i.setState(i.state);
912   }
913
914   handleSiteReportsEmailAdmins(i: SiteForm, event: any) {
915     i.state.siteForm.reports_email_admins = event.target.checked;
916     i.setState(i.state);
917   }
918
919   handleSitePrivateInstance(i: SiteForm, event: any) {
920     i.state.siteForm.private_instance = event.target.checked;
921     i.setState(i.state);
922   }
923
924   handleSiteHideModlogModNames(i: SiteForm, event: any) {
925     i.state.siteForm.hide_modlog_mod_names = event.target.checked;
926     i.setState(i.state);
927   }
928
929   handleSiteDefaultTheme(i: SiteForm, event: any) {
930     i.state.siteForm.default_theme = event.target.value;
931     i.setState(i.state);
932   }
933
934   handleIconUpload(url: string) {
935     this.setState(s => ((s.siteForm.icon = url), s));
936   }
937
938   handleIconRemove() {
939     this.setState(s => ((s.siteForm.icon = ""), s));
940   }
941
942   handleBannerUpload(url: string) {
943     this.setState(s => ((s.siteForm.banner = url), s));
944   }
945
946   handleBannerRemove() {
947     this.setState(s => ((s.siteForm.banner = ""), s));
948   }
949
950   handleSiteSlurFilterRegex(i: SiteForm, event: any) {
951     i.setState(s => ((s.siteForm.slur_filter_regex = event.target.value), s));
952   }
953
954   handleSiteActorNameMaxLength(i: SiteForm, event: any) {
955     i.setState(
956       s => ((s.siteForm.actor_name_max_length = Number(event.target.value)), s)
957     );
958   }
959
960   handleSiteFederationEnabled(i: SiteForm, event: any) {
961     i.state.siteForm.federation_enabled = event.target.checked;
962     i.setState(i.state);
963   }
964
965   handleSiteFederationDebug(i: SiteForm, event: any) {
966     i.state.siteForm.federation_debug = event.target.checked;
967     i.setState(i.state);
968   }
969
970   handleSiteFederationWorkerCount(i: SiteForm, event: any) {
971     i.setState(
972       s => (
973         (s.siteForm.federation_worker_count = Number(event.target.value)), s
974       )
975     );
976   }
977
978   handleSiteCaptchaEnabled(i: SiteForm, event: any) {
979     i.state.siteForm.captcha_enabled = event.target.checked;
980     i.setState(i.state);
981   }
982
983   handleSiteCaptchaDifficulty(i: SiteForm, event: any) {
984     i.setState(s => ((s.siteForm.captcha_difficulty = event.target.value), s));
985   }
986
987   handleDiscussionLanguageChange(val: number[]) {
988     this.setState(s => ((s.siteForm.discussion_languages = val), s));
989   }
990
991   handleDefaultPostListingTypeChange(val: ListingType) {
992     this.setState(s => ((s.siteForm.default_post_listing_type = val), s));
993   }
994 }