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