]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/site-form.tsx
break out browser and helper methods
[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 { capitalizeFirstLetter, myAuthRequired } from "../../utils";
16 import { Icon, Spinner } from "../common/icon";
17 import { ImageUploadForm } from "../common/image-upload-form";
18 import { LanguageSelect } from "../common/language-select";
19 import { ListingTypeSelect } from "../common/listing-type-select";
20 import { MarkdownTextArea } from "../common/markdown-textarea";
21 import NavigationPrompt from "../common/navigation-prompt";
22
23 interface SiteFormProps {
24   blockedInstances?: Instance[];
25   allowedInstances?: Instance[];
26   showLocal?: boolean;
27   themeList?: string[];
28   onSaveSite(form: EditSite): void;
29   siteRes: GetSiteResponse;
30 }
31
32 interface SiteFormState {
33   siteForm: EditSite;
34   loading: boolean;
35   instance_select: {
36     allowed_instances: string;
37     blocked_instances: string;
38   };
39   submitted: boolean;
40 }
41
42 type InstanceKey = "allowed_instances" | "blocked_instances";
43
44 export class SiteForm extends Component<SiteFormProps, SiteFormState> {
45   state: SiteFormState = {
46     siteForm: this.initSiteForm(),
47     loading: false,
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_debug: ls.federation_debug,
82       federation_worker_count: ls.federation_worker_count,
83       captcha_enabled: ls.captcha_enabled,
84       captcha_difficulty: ls.captcha_difficulty,
85       allowed_instances: this.props.allowedInstances?.map(i => i.domain),
86       blocked_instances: this.props.blockedInstances?.map(i => i.domain),
87       auth: "TODO",
88     };
89   }
90
91   constructor(props: any, context: any) {
92     super(props, context);
93
94     this.handleSiteSidebarChange = this.handleSiteSidebarChange.bind(this);
95     this.handleSiteLegalInfoChange = this.handleSiteLegalInfoChange.bind(this);
96     this.handleSiteApplicationQuestionChange =
97       this.handleSiteApplicationQuestionChange.bind(this);
98
99     this.handleIconUpload = this.handleIconUpload.bind(this);
100     this.handleIconRemove = this.handleIconRemove.bind(this);
101
102     this.handleBannerUpload = this.handleBannerUpload.bind(this);
103     this.handleBannerRemove = this.handleBannerRemove.bind(this);
104
105     this.handleDefaultPostListingTypeChange =
106       this.handleDefaultPostListingTypeChange.bind(this);
107
108     this.handleDiscussionLanguageChange =
109       this.handleDiscussionLanguageChange.bind(this);
110     this.handleAddInstance = this.handleAddInstance.bind(this);
111     this.handleInstanceEnterPress = this.handleInstanceEnterPress.bind(this);
112     this.handleInstanceTextChange = this.handleInstanceTextChange.bind(this);
113   }
114
115   // Necessary to stop the loading
116   componentWillReceiveProps() {
117     this.setState({ loading: false });
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.state.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("save"))
140             : capitalizeFirstLetter(i18n.t("name"))
141         } ${i18n.t("your_site")}`}</h5>
142         <div className="form-group 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="form-group">
160           <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="form-group">
170           <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="form-group 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="form-group 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="form-group 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="form-group 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="form-group 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="form-group row">
258           <div className="col-12">
259             <label
260               className="form-check-label mr-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="custom-select 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="form-group 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="form-group 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="form-group 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="form-group 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="form-group 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="form-group row">
381           <div className="col-12">
382             <label
383               className="form-check-label mr-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="custom-select 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="form-group row">
405             <label className="col-sm-3">{i18n.t("listing_type")}</label>
406             <div className="col-sm-9">
407               <ListingTypeSelect
408                 type_={this.state.siteForm.default_post_listing_type ?? "Local"}
409                 showLocal
410                 showSubscribed={false}
411                 onChange={this.handleDefaultPostListingTypeChange}
412               />
413             </div>
414           </form>
415         )}
416         <div className="form-group row">
417           <div className="col-12">
418             <div className="form-check">
419               <input
420                 className="form-check-input"
421                 id="create-site-private-instance"
422                 type="checkbox"
423                 checked={this.state.siteForm.private_instance}
424                 onChange={linkEvent(this, this.handleSitePrivateInstance)}
425               />
426               <label
427                 className="form-check-label"
428                 htmlFor="create-site-private-instance"
429               >
430                 {i18n.t("private_instance")}
431               </label>
432             </div>
433           </div>
434         </div>
435         <div className="form-group row">
436           <div className="col-12">
437             <div className="form-check">
438               <input
439                 className="form-check-input"
440                 id="create-site-hide-modlog-mod-names"
441                 type="checkbox"
442                 checked={this.state.siteForm.hide_modlog_mod_names}
443                 onChange={linkEvent(this, this.handleSiteHideModlogModNames)}
444               />
445               <label
446                 className="form-check-label"
447                 htmlFor="create-site-hide-modlog-mod-names"
448               >
449                 {i18n.t("hide_modlog_mod_names")}
450               </label>
451             </div>
452           </div>
453         </div>
454         <div className="form-group row">
455           <label
456             className="col-12 col-form-label"
457             htmlFor="create-site-slur-filter-regex"
458           >
459             {i18n.t("slur_filter_regex")}
460           </label>
461           <div className="col-12">
462             <input
463               type="text"
464               id="create-site-slur-filter-regex"
465               placeholder="(word1|word2)"
466               className="form-control"
467               value={this.state.siteForm.slur_filter_regex}
468               onInput={linkEvent(this, this.handleSiteSlurFilterRegex)}
469               minLength={3}
470             />
471           </div>
472         </div>
473         <LanguageSelect
474           allLanguages={this.props.siteRes.all_languages}
475           siteLanguages={this.props.siteRes.discussion_languages}
476           selectedLanguageIds={this.state.siteForm.discussion_languages}
477           multiple={true}
478           onChange={this.handleDiscussionLanguageChange}
479           showAll
480         />
481         <div className="form-group row">
482           <label
483             className="col-12 col-form-label"
484             htmlFor="create-site-actor-name"
485           >
486             {i18n.t("actor_name_max_length")}
487           </label>
488           <div className="col-12">
489             <input
490               type="number"
491               id="create-site-actor-name"
492               className="form-control"
493               min={5}
494               value={this.state.siteForm.actor_name_max_length}
495               onInput={linkEvent(this, this.handleSiteActorNameMaxLength)}
496             />
497           </div>
498         </div>
499         <div className="form-group row">
500           <div className="col-12">
501             <div className="form-check">
502               <input
503                 className="form-check-input"
504                 id="create-site-federation-enabled"
505                 type="checkbox"
506                 checked={this.state.siteForm.federation_enabled}
507                 onChange={linkEvent(this, this.handleSiteFederationEnabled)}
508               />
509               <label
510                 className="form-check-label"
511                 htmlFor="create-site-federation-enabled"
512               >
513                 {i18n.t("federation_enabled")}
514               </label>
515             </div>
516           </div>
517         </div>
518         {this.state.siteForm.federation_enabled && (
519           <>
520             <div className="form-group row">
521               {this.federatedInstanceSelect("allowed_instances")}
522               {this.federatedInstanceSelect("blocked_instances")}
523             </div>
524             <div className="form-group row">
525               <div className="col-12">
526                 <div className="form-check">
527                   <input
528                     className="form-check-input"
529                     id="create-site-federation-debug"
530                     type="checkbox"
531                     checked={this.state.siteForm.federation_debug}
532                     onChange={linkEvent(this, this.handleSiteFederationDebug)}
533                   />
534                   <label
535                     className="form-check-label"
536                     htmlFor="create-site-federation-debug"
537                   >
538                     {i18n.t("federation_debug")}
539                   </label>
540                 </div>
541               </div>
542             </div>
543             <div className="form-group row">
544               <label
545                 className="col-12 col-form-label"
546                 htmlFor="create-site-federation-worker-count"
547               >
548                 {i18n.t("federation_worker_count")}
549               </label>
550               <div className="col-12">
551                 <input
552                   type="number"
553                   id="create-site-federation-worker-count"
554                   className="form-control"
555                   min={0}
556                   value={this.state.siteForm.federation_worker_count}
557                   onInput={linkEvent(
558                     this,
559                     this.handleSiteFederationWorkerCount
560                   )}
561                 />
562               </div>
563             </div>
564           </>
565         )}
566         <div className="form-group row">
567           <div className="col-12">
568             <div className="form-check">
569               <input
570                 className="form-check-input"
571                 id="create-site-captcha-enabled"
572                 type="checkbox"
573                 checked={this.state.siteForm.captcha_enabled}
574                 onChange={linkEvent(this, this.handleSiteCaptchaEnabled)}
575               />
576               <label
577                 className="form-check-label"
578                 htmlFor="create-site-captcha-enabled"
579               >
580                 {i18n.t("captcha_enabled")}
581               </label>
582             </div>
583           </div>
584         </div>
585         {this.state.siteForm.captcha_enabled && (
586           <div className="form-group row">
587             <div className="col-12">
588               <label
589                 className="form-check-label mr-2"
590                 htmlFor="create-site-captcha-difficulty"
591               >
592                 {i18n.t("captcha_difficulty")}
593               </label>
594               <select
595                 id="create-site-captcha-difficulty"
596                 value={this.state.siteForm.captcha_difficulty}
597                 onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
598                 className="custom-select w-auto"
599               >
600                 <option value="easy">{i18n.t("easy")}</option>
601                 <option value="medium">{i18n.t("medium")}</option>
602                 <option value="hard">{i18n.t("hard")}</option>
603               </select>
604             </div>
605           </div>
606         )}
607         <div className="form-group row">
608           <div className="col-12">
609             <button
610               type="submit"
611               className="btn btn-secondary mr-2"
612               disabled={this.state.loading}
613             >
614               {this.state.loading ? (
615                 <Spinner />
616               ) : siteSetup ? (
617                 capitalizeFirstLetter(i18n.t("save"))
618               ) : (
619                 capitalizeFirstLetter(i18n.t("create"))
620               )}
621             </button>
622           </div>
623         </div>
624       </form>
625     );
626   }
627
628   federatedInstanceSelect(key: InstanceKey) {
629     const id = `create_site_${key}`;
630     const value = this.state.instance_select[key];
631     const selectedInstances = this.state.siteForm[key];
632     return (
633       <div className="col-12 col-md-6">
634         <label className="col-form-label" htmlFor={id}>
635           {i18n.t(key)}
636         </label>
637         <div className="d-flex justify-content-between align-items-center">
638           <input
639             type="text"
640             placeholder="instance.tld"
641             id={id}
642             className="form-control"
643             value={value}
644             onInput={linkEvent(key, this.handleInstanceTextChange)}
645             onKeyUp={linkEvent(key, this.handleInstanceEnterPress)}
646           />
647           <button
648             type="button"
649             className="btn btn-sm bg-success ml-2"
650             onClick={linkEvent(key, this.handleAddInstance)}
651             style={"width: 2rem; height: 2rem;"}
652             tabIndex={
653               -1 /* Making this untabble because handling enter key in text input makes keyboard support for this button redundant */
654             }
655           >
656             <Icon
657               icon="add"
658               classes="icon-inline text-light m-auto d-block position-static"
659             />
660           </button>
661         </div>
662         {selectedInstances && selectedInstances.length > 0 && (
663           <ul className="mt-3 list-unstyled w-100 d-flex flex-column justify-content-around align-items-center">
664             {selectedInstances.map(instance => (
665               <li
666                 key={instance}
667                 className="my-1 w-100 w-md-75 d-flex align-items-center justify-content-between"
668               >
669                 <label className="d-block m-0 w-100 " htmlFor={instance}>
670                   <strong>{instance}</strong>
671                 </label>
672                 <button
673                   id={instance}
674                   type="button"
675                   style={"width: 2rem; height: 2rem;"}
676                   className="btn btn-sm bg-danger"
677                   onClick={linkEvent(
678                     { key, instance },
679                     this.handleRemoveInstance
680                   )}
681                 >
682                   <Icon
683                     icon="x"
684                     classes="icon-inline text-light m-auto d-block position-static"
685                   />
686                 </button>
687               </li>
688             ))}
689           </ul>
690         )}
691       </div>
692     );
693   }
694
695   handleInstanceTextChange(type: InstanceKey, event: any) {
696     this.setState(s => ({
697       ...s,
698       instance_select: {
699         ...s.instance_select,
700         [type]: event.target.value,
701       },
702     }));
703   }
704
705   handleInstanceEnterPress(
706     key: InstanceKey,
707     event: InfernoKeyboardEvent<HTMLInputElement>
708   ) {
709     if (event.code.toLowerCase() === "enter") {
710       event.preventDefault();
711
712       this.handleAddInstance(key);
713     }
714   }
715
716   handleSaveSiteSubmit(i: SiteForm, event: any) {
717     event.preventDefault();
718     const auth = myAuthRequired();
719     i.setState(s => ((s.siteForm.auth = auth), s));
720     i.setState({ loading: true, submitted: true });
721
722     const stateSiteForm = i.state.siteForm;
723
724     let form: EditSite | CreateSite;
725
726     if (i.props.siteRes.site_view.local_site.site_setup) {
727       form = stateSiteForm;
728     } else {
729       form = {
730         name: stateSiteForm.name ?? "My site",
731         sidebar: stateSiteForm.sidebar,
732         description: stateSiteForm.description,
733         icon: stateSiteForm.icon,
734         banner: stateSiteForm.banner,
735         community_creation_admin_only:
736           stateSiteForm.community_creation_admin_only,
737         enable_nsfw: stateSiteForm.enable_nsfw,
738         enable_downvotes: stateSiteForm.enable_downvotes,
739         application_question: stateSiteForm.application_question,
740         registration_mode: stateSiteForm.registration_mode,
741         require_email_verification: stateSiteForm.require_email_verification,
742         private_instance: stateSiteForm.private_instance,
743         default_theme: stateSiteForm.default_theme,
744         default_post_listing_type: stateSiteForm.default_post_listing_type,
745         application_email_admins: stateSiteForm.application_email_admins,
746         hide_modlog_mod_names: stateSiteForm.hide_modlog_mod_names,
747         legal_information: stateSiteForm.legal_information,
748         slur_filter_regex: stateSiteForm.slur_filter_regex,
749         actor_name_max_length: stateSiteForm.actor_name_max_length,
750         rate_limit_message: stateSiteForm.rate_limit_message,
751         rate_limit_message_per_second:
752           stateSiteForm.rate_limit_message_per_second,
753         rate_limit_comment: stateSiteForm.rate_limit_comment,
754         rate_limit_comment_per_second:
755           stateSiteForm.rate_limit_comment_per_second,
756         rate_limit_image: stateSiteForm.rate_limit_image,
757         rate_limit_image_per_second: stateSiteForm.rate_limit_image_per_second,
758         rate_limit_post: stateSiteForm.rate_limit_post,
759         rate_limit_post_per_second: stateSiteForm.rate_limit_post_per_second,
760         rate_limit_register: stateSiteForm.rate_limit_register,
761         rate_limit_register_per_second:
762           stateSiteForm.rate_limit_register_per_second,
763         rate_limit_search: stateSiteForm.rate_limit_search,
764         rate_limit_search_per_second:
765           stateSiteForm.rate_limit_search_per_second,
766         federation_enabled: stateSiteForm.federation_enabled,
767         federation_debug: stateSiteForm.federation_debug,
768         federation_worker_count: stateSiteForm.federation_worker_count,
769         captcha_enabled: stateSiteForm.captcha_enabled,
770         captcha_difficulty: stateSiteForm.captcha_difficulty,
771         allowed_instances: stateSiteForm.allowed_instances,
772         blocked_instances: stateSiteForm.blocked_instances,
773         discussion_languages: stateSiteForm.discussion_languages,
774         auth,
775       };
776     }
777
778     i.props.onSaveSite(form);
779   }
780
781   handleAddInstance(key: InstanceKey) {
782     const instance = this.state.instance_select[key].trim();
783     if (!this.state.siteForm[key]?.includes(instance)) {
784       this.setState(s => ({
785         ...s,
786         siteForm: {
787           ...s.siteForm,
788           [key]: [...(s.siteForm[key] ?? []), instance],
789         },
790         instance_select: {
791           ...s.instance_select,
792           [key]: "",
793         },
794       }));
795
796       const oppositeKey: InstanceKey =
797         key === "allowed_instances" ? "blocked_instances" : "allowed_instances";
798       if (this.state.siteForm[oppositeKey]?.includes(instance)) {
799         this.handleRemoveInstance({ key: oppositeKey, instance });
800       }
801     }
802   }
803
804   handleRemoveInstance({
805     key,
806     instance,
807   }: {
808     key: InstanceKey;
809     instance: string;
810   }) {
811     this.setState(s => ({
812       ...s,
813       siteForm: {
814         ...s.siteForm,
815         [key]: s.siteForm[key]?.filter(i => i !== instance),
816       },
817     }));
818   }
819
820   handleSiteNameChange(i: SiteForm, event: any) {
821     i.state.siteForm.name = event.target.value;
822     i.setState(i.state);
823   }
824
825   handleSiteSidebarChange(val: string) {
826     this.setState(s => ((s.siteForm.sidebar = val), s));
827   }
828
829   handleSiteLegalInfoChange(val: string) {
830     this.setState(s => ((s.siteForm.legal_information = val), s));
831   }
832
833   handleTaglineChange(i: SiteForm, index: number, val: string) {
834     const taglines = i.state.siteForm.taglines;
835     if (taglines) {
836       taglines[index] = val;
837       i.setState(i.state);
838     }
839   }
840
841   handleDeleteTaglineClick(
842     i: SiteForm,
843     index: number,
844     event: InfernoMouseEvent<HTMLButtonElement>
845   ) {
846     event.preventDefault();
847     const taglines = i.state.siteForm.taglines;
848     if (taglines) {
849       taglines.splice(index, 1);
850       i.state.siteForm.taglines = undefined;
851       i.setState(i.state);
852       i.state.siteForm.taglines = taglines;
853       i.setState(i.state);
854     }
855   }
856
857   handleAddTaglineClick(
858     i: SiteForm,
859     event: InfernoMouseEvent<HTMLButtonElement>
860   ) {
861     event.preventDefault();
862     if (!i.state.siteForm.taglines) {
863       i.state.siteForm.taglines = [];
864     }
865     i.state.siteForm.taglines.push("");
866     i.setState(i.state);
867   }
868
869   handleSiteApplicationQuestionChange(val: string) {
870     this.setState(s => ((s.siteForm.application_question = val), s));
871   }
872
873   handleSiteDescChange(i: SiteForm, event: any) {
874     i.state.siteForm.description = event.target.value;
875     i.setState(i.state);
876   }
877
878   handleSiteEnableNsfwChange(i: SiteForm, event: any) {
879     i.state.siteForm.enable_nsfw = event.target.checked;
880     i.setState(i.state);
881   }
882
883   handleSiteRegistrationModeChange(i: SiteForm, event: any) {
884     i.state.siteForm.registration_mode = event.target.value;
885     i.setState(i.state);
886   }
887
888   handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) {
889     i.state.siteForm.community_creation_admin_only = event.target.checked;
890     i.setState(i.state);
891   }
892
893   handleSiteEnableDownvotesChange(i: SiteForm, event: any) {
894     i.state.siteForm.enable_downvotes = event.target.checked;
895     i.setState(i.state);
896   }
897
898   handleSiteRequireEmailVerification(i: SiteForm, event: any) {
899     i.state.siteForm.require_email_verification = event.target.checked;
900     i.setState(i.state);
901   }
902
903   handleSiteApplicationEmailAdmins(i: SiteForm, event: any) {
904     i.state.siteForm.application_email_admins = event.target.checked;
905     i.setState(i.state);
906   }
907
908   handleSiteReportsEmailAdmins(i: SiteForm, event: any) {
909     i.state.siteForm.reports_email_admins = event.target.checked;
910     i.setState(i.state);
911   }
912
913   handleSitePrivateInstance(i: SiteForm, event: any) {
914     i.state.siteForm.private_instance = event.target.checked;
915     i.setState(i.state);
916   }
917
918   handleSiteHideModlogModNames(i: SiteForm, event: any) {
919     i.state.siteForm.hide_modlog_mod_names = event.target.checked;
920     i.setState(i.state);
921   }
922
923   handleSiteDefaultTheme(i: SiteForm, event: any) {
924     i.state.siteForm.default_theme = event.target.value;
925     i.setState(i.state);
926   }
927
928   handleIconUpload(url: string) {
929     this.setState(s => ((s.siteForm.icon = url), s));
930   }
931
932   handleIconRemove() {
933     this.setState(s => ((s.siteForm.icon = ""), s));
934   }
935
936   handleBannerUpload(url: string) {
937     this.setState(s => ((s.siteForm.banner = url), s));
938   }
939
940   handleBannerRemove() {
941     this.setState(s => ((s.siteForm.banner = ""), s));
942   }
943
944   handleSiteSlurFilterRegex(i: SiteForm, event: any) {
945     i.setState(s => ((s.siteForm.slur_filter_regex = event.target.value), s));
946   }
947
948   handleSiteActorNameMaxLength(i: SiteForm, event: any) {
949     i.setState(
950       s => ((s.siteForm.actor_name_max_length = Number(event.target.value)), s)
951     );
952   }
953
954   handleSiteFederationEnabled(i: SiteForm, event: any) {
955     i.state.siteForm.federation_enabled = event.target.checked;
956     i.setState(i.state);
957   }
958
959   handleSiteFederationDebug(i: SiteForm, event: any) {
960     i.state.siteForm.federation_debug = event.target.checked;
961     i.setState(i.state);
962   }
963
964   handleSiteFederationWorkerCount(i: SiteForm, event: any) {
965     i.setState(
966       s => (
967         (s.siteForm.federation_worker_count = Number(event.target.value)), s
968       )
969     );
970   }
971
972   handleSiteCaptchaEnabled(i: SiteForm, event: any) {
973     i.state.siteForm.captcha_enabled = event.target.checked;
974     i.setState(i.state);
975   }
976
977   handleSiteCaptchaDifficulty(i: SiteForm, event: any) {
978     i.setState(s => ((s.siteForm.captcha_difficulty = event.target.value), s));
979   }
980
981   handleDiscussionLanguageChange(val: number[]) {
982     this.setState(s => ((s.siteForm.discussion_languages = val), s));
983   }
984
985   handleDefaultPostListingTypeChange(val: ListingType) {
986     this.setState(s => ((s.siteForm.default_post_listing_type = val), s));
987   }
988 }