1 import { myAuthRequired } from "@utils/app";
2 import { capitalizeFirstLetter, validInstanceTLD } from "@utils/helpers";
16 } from "lemmy-js-client";
17 import deepEqual from "lodash.isequal";
18 import { I18NextService } from "../../services";
19 import { Icon, Spinner } from "../common/icon";
20 import { ImageUploadForm } from "../common/image-upload-form";
21 import { LanguageSelect } from "../common/language-select";
22 import { ListingTypeSelect } from "../common/listing-type-select";
23 import { MarkdownTextArea } from "../common/markdown-textarea";
24 import NavigationPrompt from "../common/navigation-prompt";
26 interface SiteFormProps {
27 blockedInstances?: Instance[];
28 allowedInstances?: Instance[];
31 onSaveSite(form: EditSite): void;
32 siteRes: GetSiteResponse;
36 interface SiteFormState {
39 allowed_instances: string;
40 blocked_instances: string;
45 type InstanceKey = "allowed_instances" | "blocked_instances";
47 export class SiteForm extends Component<SiteFormProps, SiteFormState> {
48 state: SiteFormState = {
49 siteForm: this.initSiteForm(),
51 allowed_instances: "",
52 blocked_instances: "",
57 initSiteForm(): EditSite {
58 const site = this.props.siteRes.site_view.site;
59 const ls = this.props.siteRes.site_view.local_site;
63 sidebar: site.sidebar,
64 description: site.description,
65 enable_downvotes: ls.enable_downvotes,
66 registration_mode: ls.registration_mode,
67 enable_nsfw: ls.enable_nsfw,
68 community_creation_admin_only: ls.community_creation_admin_only,
71 require_email_verification: ls.require_email_verification,
72 application_question: ls.application_question,
73 private_instance: ls.private_instance,
74 default_theme: ls.default_theme,
75 default_post_listing_type: ls.default_post_listing_type,
76 legal_information: ls.legal_information,
77 application_email_admins: ls.application_email_admins,
78 reports_email_admins: ls.reports_email_admins,
79 hide_modlog_mod_names: ls.hide_modlog_mod_names,
80 discussion_languages: this.props.siteRes.discussion_languages,
81 slur_filter_regex: ls.slur_filter_regex,
82 actor_name_max_length: ls.actor_name_max_length,
83 federation_enabled: ls.federation_enabled,
84 captcha_enabled: ls.captcha_enabled,
85 captcha_difficulty: ls.captcha_difficulty,
86 allowed_instances: this.props.allowedInstances?.map(i => i.domain),
87 blocked_instances: this.props.blockedInstances?.map(i => i.domain),
92 constructor(props: any, context: any) {
93 super(props, context);
95 this.handleSiteSidebarChange = this.handleSiteSidebarChange.bind(this);
96 this.handleSiteLegalInfoChange = this.handleSiteLegalInfoChange.bind(this);
97 this.handleSiteApplicationQuestionChange =
98 this.handleSiteApplicationQuestionChange.bind(this);
100 this.handleIconUpload = this.handleIconUpload.bind(this);
101 this.handleIconRemove = this.handleIconRemove.bind(this);
103 this.handleBannerUpload = this.handleBannerUpload.bind(this);
104 this.handleBannerRemove = this.handleBannerRemove.bind(this);
106 this.handleDefaultPostListingTypeChange =
107 this.handleDefaultPostListingTypeChange.bind(this);
109 this.handleDiscussionLanguageChange =
110 this.handleDiscussionLanguageChange.bind(this);
112 this.handleAddInstance = this.handleAddInstance.bind(this);
113 this.handleRemoveInstance = this.handleRemoveInstance.bind(this);
115 this.handleInstanceEnterPress = this.handleInstanceEnterPress.bind(this);
116 this.handleInstanceTextChange = this.handleInstanceTextChange.bind(this);
120 const siteSetup = this.props.siteRes.site_view.local_site.site_setup;
123 className="site-form"
124 onSubmit={linkEvent(this, this.handleSaveSiteSubmit)}
128 !this.props.loading &&
131 this.state.siteForm.name ||
132 this.state.siteForm.sidebar ||
133 this.state.siteForm.application_question ||
134 this.state.siteForm.description
136 !this.state.submitted
141 ? capitalizeFirstLetter(I18NextService.i18n.t("edit"))
142 : capitalizeFirstLetter(I18NextService.i18n.t("setup"))
143 } ${I18NextService.i18n.t("your_site")}`}</h5>
144 <div className="mb-3 row">
145 <label className="col-12 col-form-label" htmlFor="create-site-name">
146 {I18NextService.i18n.t("name")}
148 <div className="col-12">
151 id="create-site-name"
152 className="form-control"
153 value={this.state.siteForm.name}
154 onInput={linkEvent(this, this.handleSiteNameChange)}
161 <div className="input-group mb-3">
162 <label className="me-2 col-form-label">
163 {I18NextService.i18n.t("icon")}
166 uploadTitle={I18NextService.i18n.t("upload_icon")}
167 imageSrc={this.state.siteForm.icon}
168 onUpload={this.handleIconUpload}
169 onRemove={this.handleIconRemove}
173 <div className="input-group mb-3">
174 <label className="me-2 col-form-label">
175 {I18NextService.i18n.t("banner")}
178 uploadTitle={I18NextService.i18n.t("upload_banner")}
179 imageSrc={this.state.siteForm.banner}
180 onUpload={this.handleBannerUpload}
181 onRemove={this.handleBannerRemove}
184 <div className="mb-3 row">
185 <label className="col-12 col-form-label" htmlFor="site-desc">
186 {I18NextService.i18n.t("description")}
188 <div className="col-12">
191 className="form-control"
193 value={this.state.siteForm.description}
194 onInput={linkEvent(this, this.handleSiteDescChange)}
199 <div className="mb-3 row">
200 <label className="col-12 col-form-label">
201 {I18NextService.i18n.t("sidebar")}
203 <div className="col-12">
205 initialContent={this.state.siteForm.sidebar}
206 onContentChange={this.handleSiteSidebarChange}
207 hideNavigationWarnings
213 <div className="mb-3 row">
214 <label className="col-12 col-form-label">
215 {I18NextService.i18n.t("legal_information")}
217 <div className="col-12">
219 initialContent={this.state.siteForm.legal_information}
220 onContentChange={this.handleSiteLegalInfoChange}
221 hideNavigationWarnings
227 <div className="mb-3 row">
228 <div className="col-12">
229 <div className="form-check">
231 className="form-check-input"
232 id="create-site-downvotes"
234 checked={this.state.siteForm.enable_downvotes}
235 onChange={linkEvent(this, this.handleSiteEnableDownvotesChange)}
238 className="form-check-label"
239 htmlFor="create-site-downvotes"
241 {I18NextService.i18n.t("enable_downvotes")}
246 <div className="mb-3 row">
247 <div className="col-12">
248 <div className="form-check">
250 className="form-check-input"
251 id="create-site-enable-nsfw"
253 checked={this.state.siteForm.enable_nsfw}
254 onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
257 className="form-check-label"
258 htmlFor="create-site-enable-nsfw"
260 {I18NextService.i18n.t("enable_nsfw")}
265 <div className="mb-3 row">
266 <div className="col-12">
268 className="form-check-label me-2"
269 htmlFor="create-site-registration-mode"
271 {I18NextService.i18n.t("registration_mode")}
274 id="create-site-registration-mode"
275 value={this.state.siteForm.registration_mode}
276 onChange={linkEvent(this, this.handleSiteRegistrationModeChange)}
277 className="form-select d-inline-block w-auto"
279 <option value={"RequireApplication"}>
280 {I18NextService.i18n.t("require_registration_application")}
282 <option value={"Open"}>
283 {I18NextService.i18n.t("open_registration")}
285 <option value={"Closed"}>
286 {I18NextService.i18n.t("close_registration")}
291 {this.state.siteForm.registration_mode == "RequireApplication" && (
292 <div className="mb-3 row">
293 <label className="col-12 col-form-label">
294 {I18NextService.i18n.t("application_questionnaire")}
296 <div className="col-12">
298 initialContent={this.state.siteForm.application_question}
299 onContentChange={this.handleSiteApplicationQuestionChange}
300 hideNavigationWarnings
307 <div className="mb-3 row">
308 <div className="col-12">
309 <div className="form-check">
311 className="form-check-input"
312 id="create-site-community-creation-admin-only"
314 checked={this.state.siteForm.community_creation_admin_only}
317 this.handleSiteCommunityCreationAdminOnly
321 className="form-check-label"
322 htmlFor="create-site-community-creation-admin-only"
324 {I18NextService.i18n.t("community_creation_admin_only")}
329 <div className="mb-3 row">
330 <div className="col-12">
331 <div className="form-check">
333 className="form-check-input"
334 id="create-site-require-email-verification"
336 checked={this.state.siteForm.require_email_verification}
339 this.handleSiteRequireEmailVerification
343 className="form-check-label"
344 htmlFor="create-site-require-email-verification"
346 {I18NextService.i18n.t("require_email_verification")}
351 <div className="mb-3 row">
352 <div className="col-12">
353 <div className="form-check">
355 className="form-check-input"
356 id="create-site-application-email-admins"
358 checked={this.state.siteForm.application_email_admins}
361 this.handleSiteApplicationEmailAdmins
365 className="form-check-label"
366 htmlFor="create-site-email-admins"
368 {I18NextService.i18n.t("application_email_admins")}
373 <div className="mb-3 row">
374 <div className="col-12">
375 <div className="form-check">
377 className="form-check-input"
378 id="create-site-reports-email-admins"
380 checked={this.state.siteForm.reports_email_admins}
381 onChange={linkEvent(this, this.handleSiteReportsEmailAdmins)}
384 className="form-check-label"
385 htmlFor="create-site-reports-email-admins"
387 {I18NextService.i18n.t("reports_email_admins")}
392 <div className="mb-3 row">
393 <div className="col-12">
395 className="form-check-label me-2"
396 htmlFor="create-site-default-theme"
398 {I18NextService.i18n.t("theme")}
401 id="create-site-default-theme"
402 value={this.state.siteForm.default_theme}
403 onChange={linkEvent(this, this.handleSiteDefaultTheme)}
404 className="form-select d-inline-block w-auto"
406 <option value="browser">
407 {I18NextService.i18n.t("browser_default")}
409 {this.props.themeList?.map(theme => (
410 <option key={theme} value={theme}>
417 {this.props.showLocal && (
418 <form className="mb-3 row">
419 <label className="col-sm-3 col-form-label">
420 {I18NextService.i18n.t("listing_type")}
422 <div className="col-sm-9">
424 type_={this.state.siteForm.default_post_listing_type ?? "Local"}
426 showSubscribed={false}
427 onChange={this.handleDefaultPostListingTypeChange}
432 <div className="mb-3 row">
433 <div className="col-12">
434 <div className="form-check">
436 className="form-check-input"
437 id="create-site-private-instance"
439 checked={this.state.siteForm.private_instance}
440 onChange={linkEvent(this, this.handleSitePrivateInstance)}
443 className="form-check-label"
444 htmlFor="create-site-private-instance"
446 {I18NextService.i18n.t("private_instance")}
451 <div className="mb-3 row">
452 <div className="col-12">
453 <div className="form-check">
455 className="form-check-input"
456 id="create-site-hide-modlog-mod-names"
458 checked={this.state.siteForm.hide_modlog_mod_names}
459 onChange={linkEvent(this, this.handleSiteHideModlogModNames)}
462 className="form-check-label"
463 htmlFor="create-site-hide-modlog-mod-names"
465 {I18NextService.i18n.t("hide_modlog_mod_names")}
470 <div className="mb-3 row">
472 className="col-12 col-form-label"
473 htmlFor="create-site-slur-filter-regex"
475 {I18NextService.i18n.t("slur_filter_regex")}
477 <div className="col-12">
480 id="create-site-slur-filter-regex"
481 placeholder="(word1|word2)"
482 className="form-control"
483 value={this.state.siteForm.slur_filter_regex}
484 onInput={linkEvent(this, this.handleSiteSlurFilterRegex)}
490 allLanguages={this.props.siteRes.all_languages}
491 siteLanguages={this.props.siteRes.discussion_languages}
492 selectedLanguageIds={this.state.siteForm.discussion_languages}
494 onChange={this.handleDiscussionLanguageChange}
497 <div className="mb-3 row">
499 className="col-12 col-form-label"
500 htmlFor="create-site-actor-name"
502 {I18NextService.i18n.t("actor_name_max_length")}
504 <div className="col-12">
507 id="create-site-actor-name"
508 className="form-control"
510 value={this.state.siteForm.actor_name_max_length}
511 onInput={linkEvent(this, this.handleSiteActorNameMaxLength)}
515 <div className="mb-3 row">
516 <div className="col-12">
517 <div className="form-check">
519 className="form-check-input"
520 id="create-site-federation-enabled"
522 checked={this.state.siteForm.federation_enabled}
523 onChange={linkEvent(this, this.handleSiteFederationEnabled)}
526 className="form-check-label"
527 htmlFor="create-site-federation-enabled"
529 {I18NextService.i18n.t("federation_enabled")}
534 {this.state.siteForm.federation_enabled && (
536 <div className="mb-3 row">
537 {this.federatedInstanceSelect("allowed_instances")}
538 {this.federatedInstanceSelect("blocked_instances")}
540 <div className="mb-3 row">
541 <div className="col-12">
542 <div className="form-check">
544 className="form-check-input"
545 id="create-site-federation-debug"
547 checked={this.state.siteForm.federation_debug}
548 onChange={linkEvent(this, this.handleSiteFederationDebug)}
551 className="form-check-label"
552 htmlFor="create-site-federation-debug"
554 {I18NextService.i18n.t("federation_debug")}
561 <div className="mb-3 row">
562 <div className="col-12">
563 <div className="form-check">
565 className="form-check-input"
566 id="create-site-captcha-enabled"
568 checked={this.state.siteForm.captcha_enabled}
569 onChange={linkEvent(this, this.handleSiteCaptchaEnabled)}
572 className="form-check-label"
573 htmlFor="create-site-captcha-enabled"
575 {I18NextService.i18n.t("captcha_enabled")}
580 {this.state.siteForm.captcha_enabled && (
581 <div className="mb-3 row">
582 <div className="col-12">
584 className="form-check-label me-2"
585 htmlFor="create-site-captcha-difficulty"
587 {I18NextService.i18n.t("captcha_difficulty")}
590 id="create-site-captcha-difficulty"
591 value={this.state.siteForm.captcha_difficulty}
592 onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
593 className="form-select d-inline-block w-auto"
595 <option value="easy">{I18NextService.i18n.t("easy")}</option>
596 <option value="medium">
597 {I18NextService.i18n.t("medium")}
599 <option value="hard">{I18NextService.i18n.t("hard")}</option>
604 <div className="mb-3 row">
605 <div className="col-12">
608 className="btn btn-secondary me-2"
609 disabled={this.props.loading}
611 {this.props.loading ? (
614 capitalizeFirstLetter(I18NextService.i18n.t("save"))
616 capitalizeFirstLetter(I18NextService.i18n.t("create"))
626 prevProps: Readonly<{ children?: InfernoNode } & SiteFormProps>
630 deepEqual(prevProps.allowedInstances, this.props.allowedInstances) ||
631 deepEqual(prevProps.blockedInstances, this.props.blockedInstances)
634 this.setState({ siteForm: this.initSiteForm() });
638 federatedInstanceSelect(key: InstanceKey) {
639 const id = `create_site_${key}`;
640 const value = this.state.instance_select[key];
641 const selectedInstances = this.state.siteForm[key];
643 <div className="col-12 col-md-6">
644 <label className="col-form-label" htmlFor={id}>
645 {I18NextService.i18n.t(key)}
647 <div className="d-flex justify-content-between align-items-center">
650 placeholder="instance.tld"
652 className="form-control"
654 onInput={linkEvent(key, this.handleInstanceTextChange)}
655 onKeyUp={linkEvent(key, this.handleInstanceEnterPress)}
659 className="btn btn-sm bg-success ms-2"
660 onClick={linkEvent(key, this.handleAddInstance)}
661 style={"width: 2rem; height: 2rem;"}
663 -1 /* Making this untabble because handling enter key in text input makes keyboard support for this button redundant */
668 classes="icon-inline text-light m-auto d-block position-static"
672 {selectedInstances && selectedInstances.length > 0 && (
673 <ul className="mt-3 list-unstyled w-100 d-flex flex-column justify-content-around align-items-center">
674 {selectedInstances.map(instance => (
677 className="my-1 w-100 w-md-75 d-flex align-items-center justify-content-between"
679 <label className="d-block m-0 w-100 " htmlFor={instance}>
680 <strong>{instance}</strong>
685 style={"width: 2rem; height: 2rem;"}
686 className="btn btn-sm bg-danger"
689 this.handleRemoveInstance
694 classes="icon-inline text-light m-auto d-block position-static"
705 handleInstanceTextChange(type: InstanceKey, event: any) {
706 this.setState(s => ({
709 ...s.instance_select,
710 [type]: event.target.value,
715 handleInstanceEnterPress(
717 event: InfernoKeyboardEvent<HTMLInputElement>
719 if (event.code.toLowerCase() === "enter") {
720 event.preventDefault();
722 this.handleAddInstance(key);
726 handleSaveSiteSubmit(i: SiteForm, event: any) {
727 event.preventDefault();
728 const auth = myAuthRequired();
729 i.setState(s => ((s.siteForm.auth = auth), s));
730 i.setState({ submitted: true });
732 const stateSiteForm = i.state.siteForm;
734 let form: EditSite | CreateSite;
736 if (i.props.siteRes.site_view.local_site.site_setup) {
737 form = stateSiteForm;
740 name: stateSiteForm.name ?? "My site",
741 sidebar: stateSiteForm.sidebar,
742 description: stateSiteForm.description,
743 icon: stateSiteForm.icon,
744 banner: stateSiteForm.banner,
745 community_creation_admin_only:
746 stateSiteForm.community_creation_admin_only,
747 enable_nsfw: stateSiteForm.enable_nsfw,
748 enable_downvotes: stateSiteForm.enable_downvotes,
749 application_question: stateSiteForm.application_question,
750 registration_mode: stateSiteForm.registration_mode,
751 require_email_verification: stateSiteForm.require_email_verification,
752 private_instance: stateSiteForm.private_instance,
753 default_theme: stateSiteForm.default_theme,
754 default_post_listing_type: stateSiteForm.default_post_listing_type,
755 application_email_admins: stateSiteForm.application_email_admins,
756 hide_modlog_mod_names: stateSiteForm.hide_modlog_mod_names,
757 legal_information: stateSiteForm.legal_information,
758 slur_filter_regex: stateSiteForm.slur_filter_regex,
759 actor_name_max_length: stateSiteForm.actor_name_max_length,
760 rate_limit_message: stateSiteForm.rate_limit_message,
761 rate_limit_message_per_second:
762 stateSiteForm.rate_limit_message_per_second,
763 rate_limit_comment: stateSiteForm.rate_limit_comment,
764 rate_limit_comment_per_second:
765 stateSiteForm.rate_limit_comment_per_second,
766 rate_limit_image: stateSiteForm.rate_limit_image,
767 rate_limit_image_per_second: stateSiteForm.rate_limit_image_per_second,
768 rate_limit_post: stateSiteForm.rate_limit_post,
769 rate_limit_post_per_second: stateSiteForm.rate_limit_post_per_second,
770 rate_limit_register: stateSiteForm.rate_limit_register,
771 rate_limit_register_per_second:
772 stateSiteForm.rate_limit_register_per_second,
773 rate_limit_search: stateSiteForm.rate_limit_search,
774 rate_limit_search_per_second:
775 stateSiteForm.rate_limit_search_per_second,
776 federation_enabled: stateSiteForm.federation_enabled,
777 federation_debug: stateSiteForm.federation_debug,
778 captcha_enabled: stateSiteForm.captcha_enabled,
779 captcha_difficulty: stateSiteForm.captcha_difficulty,
780 allowed_instances: stateSiteForm.allowed_instances,
781 blocked_instances: stateSiteForm.blocked_instances,
782 discussion_languages: stateSiteForm.discussion_languages,
787 i.props.onSaveSite(form);
790 handleAddInstance(key: InstanceKey) {
791 const instance = this.state.instance_select[key].trim();
793 if (!validInstanceTLD(instance)) {
797 if (!this.state.siteForm[key]?.includes(instance)) {
798 this.setState(s => ({
802 [key]: [...(s.siteForm[key] ?? []), instance],
805 ...s.instance_select,
810 const oppositeKey: InstanceKey =
811 key === "allowed_instances" ? "blocked_instances" : "allowed_instances";
812 if (this.state.siteForm[oppositeKey]?.includes(instance)) {
813 this.handleRemoveInstance({ key: oppositeKey, instance });
818 handleRemoveInstance({
825 this.setState(s => ({
829 [key]: s.siteForm[key]?.filter(i => i !== instance),
834 handleSiteNameChange(i: SiteForm, event: any) {
835 i.state.siteForm.name = event.target.value;
839 handleSiteSidebarChange(val: string) {
840 this.setState(s => ((s.siteForm.sidebar = val), s));
843 handleSiteLegalInfoChange(val: string) {
844 this.setState(s => ((s.siteForm.legal_information = val), s));
847 handleTaglineChange(i: SiteForm, index: number, val: string) {
848 const taglines = i.state.siteForm.taglines;
850 taglines[index] = val;
855 handleDeleteTaglineClick(
858 event: InfernoMouseEvent<HTMLButtonElement>
860 event.preventDefault();
861 const taglines = i.state.siteForm.taglines;
863 taglines.splice(index, 1);
864 i.state.siteForm.taglines = undefined;
866 i.state.siteForm.taglines = taglines;
871 handleAddTaglineClick(
873 event: InfernoMouseEvent<HTMLButtonElement>
875 event.preventDefault();
876 if (!i.state.siteForm.taglines) {
877 i.state.siteForm.taglines = [];
879 i.state.siteForm.taglines.push("");
883 handleSiteApplicationQuestionChange(val: string) {
884 this.setState(s => ((s.siteForm.application_question = val), s));
887 handleSiteDescChange(i: SiteForm, event: any) {
888 i.state.siteForm.description = event.target.value;
892 handleSiteEnableNsfwChange(i: SiteForm, event: any) {
893 i.state.siteForm.enable_nsfw = event.target.checked;
897 handleSiteRegistrationModeChange(i: SiteForm, event: any) {
898 i.state.siteForm.registration_mode = event.target.value;
902 handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) {
903 i.state.siteForm.community_creation_admin_only = event.target.checked;
907 handleSiteEnableDownvotesChange(i: SiteForm, event: any) {
908 i.state.siteForm.enable_downvotes = event.target.checked;
912 handleSiteRequireEmailVerification(i: SiteForm, event: any) {
913 i.state.siteForm.require_email_verification = event.target.checked;
917 handleSiteApplicationEmailAdmins(i: SiteForm, event: any) {
918 i.state.siteForm.application_email_admins = event.target.checked;
922 handleSiteReportsEmailAdmins(i: SiteForm, event: any) {
923 i.state.siteForm.reports_email_admins = event.target.checked;
927 handleSitePrivateInstance(i: SiteForm, event: any) {
928 i.state.siteForm.private_instance = event.target.checked;
932 handleSiteHideModlogModNames(i: SiteForm, event: any) {
933 i.state.siteForm.hide_modlog_mod_names = event.target.checked;
937 handleSiteDefaultTheme(i: SiteForm, event: any) {
938 i.state.siteForm.default_theme = event.target.value;
942 handleIconUpload(url: string) {
943 this.setState(s => ((s.siteForm.icon = url), s));
947 this.setState(s => ((s.siteForm.icon = ""), s));
950 handleBannerUpload(url: string) {
951 this.setState(s => ((s.siteForm.banner = url), s));
954 handleBannerRemove() {
955 this.setState(s => ((s.siteForm.banner = ""), s));
958 handleSiteSlurFilterRegex(i: SiteForm, event: any) {
959 i.setState(s => ((s.siteForm.slur_filter_regex = event.target.value), s));
962 handleSiteActorNameMaxLength(i: SiteForm, event: any) {
964 s => ((s.siteForm.actor_name_max_length = Number(event.target.value)), s)
968 handleSiteFederationEnabled(i: SiteForm, event: any) {
969 i.state.siteForm.federation_enabled = event.target.checked;
973 handleSiteFederationDebug(i: SiteForm, event: any) {
974 i.state.siteForm.federation_debug = event.target.checked;
978 handleSiteCaptchaEnabled(i: SiteForm, event: any) {
979 i.state.siteForm.captcha_enabled = event.target.checked;
983 handleSiteCaptchaDifficulty(i: SiteForm, event: any) {
984 i.setState(s => ((s.siteForm.captcha_difficulty = event.target.value), s));
987 handleDiscussionLanguageChange(val: number[]) {
988 this.setState(s => ((s.siteForm.discussion_languages = val), s));
991 handleDefaultPostListingTypeChange(val: ListingType) {
992 this.setState(s => ((s.siteForm.default_post_listing_type = val), s));