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