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