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