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