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="row mb-3">
162 <label className="col-sm-2 col-form-label">
163 {I18NextService.i18n.t("icon")}
165 <div className="col-sm-10">
167 uploadTitle={I18NextService.i18n.t("upload_icon")}
168 imageSrc={this.state.siteForm.icon}
169 onUpload={this.handleIconUpload}
170 onRemove={this.handleIconRemove}
175 <div className="row mb-3">
176 <label className="col-sm-2 col-form-label">
177 {I18NextService.i18n.t("banner")}
179 <div className="col-sm-10">
181 uploadTitle={I18NextService.i18n.t("upload_banner")}
182 imageSrc={this.state.siteForm.banner}
183 onUpload={this.handleBannerUpload}
184 onRemove={this.handleBannerRemove}
188 <div className="mb-3 row">
189 <label className="col-12 col-form-label" htmlFor="site-desc">
190 {I18NextService.i18n.t("description")}
192 <div className="col-12">
195 className="form-control"
197 value={this.state.siteForm.description}
198 onInput={linkEvent(this, this.handleSiteDescChange)}
203 <div className="mb-3 row">
204 <label className="col-12 col-form-label">
205 {I18NextService.i18n.t("sidebar")}
207 <div className="col-12">
209 initialContent={this.state.siteForm.sidebar}
210 onContentChange={this.handleSiteSidebarChange}
211 hideNavigationWarnings
217 <div className="mb-3 row">
218 <label className="col-12 col-form-label">
219 {I18NextService.i18n.t("legal_information")}
221 <div className="col-12">
223 initialContent={this.state.siteForm.legal_information}
224 onContentChange={this.handleSiteLegalInfoChange}
225 hideNavigationWarnings
231 <div className="mb-3 row">
232 <div className="col-12">
233 <div className="form-check">
235 className="form-check-input"
236 id="create-site-downvotes"
238 checked={this.state.siteForm.enable_downvotes}
239 onChange={linkEvent(this, this.handleSiteEnableDownvotesChange)}
242 className="form-check-label"
243 htmlFor="create-site-downvotes"
245 {I18NextService.i18n.t("enable_downvotes")}
250 <div className="mb-3 row">
251 <div className="col-12">
252 <div className="form-check">
254 className="form-check-input"
255 id="create-site-enable-nsfw"
257 checked={this.state.siteForm.enable_nsfw}
258 onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
261 className="form-check-label"
262 htmlFor="create-site-enable-nsfw"
264 {I18NextService.i18n.t("enable_nsfw")}
269 <div className="mb-3 row">
270 <div className="col-12">
272 className="form-check-label me-2"
273 htmlFor="create-site-registration-mode"
275 {I18NextService.i18n.t("registration_mode")}
278 id="create-site-registration-mode"
279 value={this.state.siteForm.registration_mode}
280 onChange={linkEvent(this, this.handleSiteRegistrationModeChange)}
281 className="form-select d-inline-block w-auto"
283 <option value={"RequireApplication"}>
284 {I18NextService.i18n.t("require_registration_application")}
286 <option value={"Open"}>
287 {I18NextService.i18n.t("open_registration")}
289 <option value={"Closed"}>
290 {I18NextService.i18n.t("close_registration")}
295 {this.state.siteForm.registration_mode == "RequireApplication" && (
296 <div className="mb-3 row">
297 <label className="col-12 col-form-label">
298 {I18NextService.i18n.t("application_questionnaire")}
300 <div className="col-12">
302 initialContent={this.state.siteForm.application_question}
303 onContentChange={this.handleSiteApplicationQuestionChange}
304 hideNavigationWarnings
311 <div className="mb-3 row">
312 <div className="col-12">
313 <div className="form-check">
315 className="form-check-input"
316 id="create-site-community-creation-admin-only"
318 checked={this.state.siteForm.community_creation_admin_only}
321 this.handleSiteCommunityCreationAdminOnly
325 className="form-check-label"
326 htmlFor="create-site-community-creation-admin-only"
328 {I18NextService.i18n.t("community_creation_admin_only")}
333 <div className="mb-3 row">
334 <div className="col-12">
335 <div className="form-check">
337 className="form-check-input"
338 id="create-site-require-email-verification"
340 checked={this.state.siteForm.require_email_verification}
343 this.handleSiteRequireEmailVerification
347 className="form-check-label"
348 htmlFor="create-site-require-email-verification"
350 {I18NextService.i18n.t("require_email_verification")}
355 <div className="mb-3 row">
356 <div className="col-12">
357 <div className="form-check">
359 className="form-check-input"
360 id="create-site-application-email-admins"
362 checked={this.state.siteForm.application_email_admins}
365 this.handleSiteApplicationEmailAdmins
369 className="form-check-label"
370 htmlFor="create-site-email-admins"
372 {I18NextService.i18n.t("application_email_admins")}
377 <div className="mb-3 row">
378 <div className="col-12">
379 <div className="form-check">
381 className="form-check-input"
382 id="create-site-reports-email-admins"
384 checked={this.state.siteForm.reports_email_admins}
385 onChange={linkEvent(this, this.handleSiteReportsEmailAdmins)}
388 className="form-check-label"
389 htmlFor="create-site-reports-email-admins"
391 {I18NextService.i18n.t("reports_email_admins")}
396 <div className="mb-3 row">
397 <div className="col-12">
399 className="form-check-label me-2"
400 htmlFor="create-site-default-theme"
402 {I18NextService.i18n.t("theme")}
405 id="create-site-default-theme"
406 value={this.state.siteForm.default_theme}
407 onChange={linkEvent(this, this.handleSiteDefaultTheme)}
408 className="form-select d-inline-block w-auto"
410 <option value="browser">
411 {I18NextService.i18n.t("browser_default")}
413 {this.props.themeList?.map(theme => (
414 <option key={theme} value={theme}>
421 {this.props.showLocal && (
422 <form className="mb-3 row">
423 <label className="col-sm-3 col-form-label">
424 {I18NextService.i18n.t("listing_type")}
426 <div className="col-sm-9">
428 type_={this.state.siteForm.default_post_listing_type ?? "Local"}
430 showSubscribed={false}
431 onChange={this.handleDefaultPostListingTypeChange}
436 <div className="mb-3 row">
437 <div className="col-12">
438 <div className="form-check">
440 className="form-check-input"
441 id="create-site-private-instance"
443 checked={this.state.siteForm.private_instance}
444 onChange={linkEvent(this, this.handleSitePrivateInstance)}
447 className="form-check-label"
448 htmlFor="create-site-private-instance"
450 {I18NextService.i18n.t("private_instance")}
455 <div className="mb-3 row">
456 <div className="col-12">
457 <div className="form-check">
459 className="form-check-input"
460 id="create-site-hide-modlog-mod-names"
462 checked={this.state.siteForm.hide_modlog_mod_names}
463 onChange={linkEvent(this, this.handleSiteHideModlogModNames)}
466 className="form-check-label"
467 htmlFor="create-site-hide-modlog-mod-names"
469 {I18NextService.i18n.t("hide_modlog_mod_names")}
474 <div className="mb-3 row">
476 className="col-12 col-form-label"
477 htmlFor="create-site-slur-filter-regex"
479 {I18NextService.i18n.t("slur_filter_regex")}
481 <div className="col-12">
484 id="create-site-slur-filter-regex"
485 placeholder="(word1|word2)"
486 className="form-control"
487 value={this.state.siteForm.slur_filter_regex}
488 onInput={linkEvent(this, this.handleSiteSlurFilterRegex)}
494 allLanguages={this.props.siteRes.all_languages}
495 siteLanguages={this.props.siteRes.discussion_languages}
496 selectedLanguageIds={this.state.siteForm.discussion_languages}
498 onChange={this.handleDiscussionLanguageChange}
501 <div className="mb-3 row">
503 className="col-12 col-form-label"
504 htmlFor="create-site-actor-name"
506 {I18NextService.i18n.t("actor_name_max_length")}
508 <div className="col-12">
511 id="create-site-actor-name"
512 className="form-control"
514 value={this.state.siteForm.actor_name_max_length}
515 onInput={linkEvent(this, this.handleSiteActorNameMaxLength)}
519 <div className="mb-3 row">
520 <div className="col-12">
521 <div className="form-check">
523 className="form-check-input"
524 id="create-site-federation-enabled"
526 checked={this.state.siteForm.federation_enabled}
527 onChange={linkEvent(this, this.handleSiteFederationEnabled)}
530 className="form-check-label"
531 htmlFor="create-site-federation-enabled"
533 {I18NextService.i18n.t("federation_enabled")}
538 {this.state.siteForm.federation_enabled && (
540 <div className="mb-3 row">
541 {this.federatedInstanceSelect("allowed_instances")}
542 {this.federatedInstanceSelect("blocked_instances")}
544 <div className="mb-3 row">
545 <div className="col-12">
546 <div className="form-check">
548 className="form-check-input"
549 id="create-site-federation-debug"
551 checked={this.state.siteForm.federation_debug}
552 onChange={linkEvent(this, this.handleSiteFederationDebug)}
555 className="form-check-label"
556 htmlFor="create-site-federation-debug"
558 {I18NextService.i18n.t("federation_debug")}
565 <div className="mb-3 row">
566 <div className="col-12">
567 <div className="form-check">
569 className="form-check-input"
570 id="create-site-captcha-enabled"
572 checked={this.state.siteForm.captcha_enabled}
573 onChange={linkEvent(this, this.handleSiteCaptchaEnabled)}
576 className="form-check-label"
577 htmlFor="create-site-captcha-enabled"
579 {I18NextService.i18n.t("captcha_enabled")}
584 {this.state.siteForm.captcha_enabled && (
585 <div className="mb-3 row">
586 <div className="col-12">
588 className="form-check-label me-2"
589 htmlFor="create-site-captcha-difficulty"
591 {I18NextService.i18n.t("captcha_difficulty")}
594 id="create-site-captcha-difficulty"
595 value={this.state.siteForm.captcha_difficulty}
596 onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
597 className="form-select d-inline-block w-auto"
599 <option value="easy">{I18NextService.i18n.t("easy")}</option>
600 <option value="medium">
601 {I18NextService.i18n.t("medium")}
603 <option value="hard">{I18NextService.i18n.t("hard")}</option>
608 <div className="mb-3 row">
609 <div className="col-12">
612 className="btn btn-secondary me-2"
613 disabled={this.props.loading}
615 {this.props.loading ? (
618 capitalizeFirstLetter(I18NextService.i18n.t("save"))
620 capitalizeFirstLetter(I18NextService.i18n.t("create"))
630 prevProps: Readonly<{ children?: InfernoNode } & SiteFormProps>
634 deepEqual(prevProps.allowedInstances, this.props.allowedInstances) ||
635 deepEqual(prevProps.blockedInstances, this.props.blockedInstances)
638 this.setState({ siteForm: this.initSiteForm() });
642 federatedInstanceSelect(key: InstanceKey) {
643 const id = `create_site_${key}`;
644 const value = this.state.instance_select[key];
645 const selectedInstances = this.state.siteForm[key];
647 <div className="col-12 col-md-6">
648 <label className="col-form-label" htmlFor={id}>
649 {I18NextService.i18n.t(key)}
651 <div className="d-flex justify-content-between align-items-center">
654 placeholder="instance.tld"
656 className="form-control"
658 onInput={linkEvent(key, this.handleInstanceTextChange)}
659 onKeyUp={linkEvent(key, this.handleInstanceEnterPress)}
663 className="btn btn-sm bg-success ms-2"
664 onClick={linkEvent(key, this.handleAddInstance)}
665 style={"width: 2rem; height: 2rem;"}
667 -1 /* Making this untabble because handling enter key in text input makes keyboard support for this button redundant */
672 classes="icon-inline text-light m-auto d-block position-static"
676 {selectedInstances && selectedInstances.length > 0 && (
677 <ul className="mt-3 list-unstyled w-100 d-flex flex-column justify-content-around align-items-center">
678 {selectedInstances.map(instance => (
681 className="my-1 w-100 w-md-75 d-flex align-items-center justify-content-between"
683 <label className="d-block m-0 w-100 " htmlFor={instance}>
684 <strong>{instance}</strong>
689 style={"width: 2rem; height: 2rem;"}
690 className="btn btn-sm bg-danger"
693 this.handleRemoveInstance
698 classes="icon-inline text-light m-auto d-block position-static"
709 handleInstanceTextChange(type: InstanceKey, event: any) {
710 this.setState(s => ({
713 ...s.instance_select,
714 [type]: event.target.value,
719 handleInstanceEnterPress(
721 event: InfernoKeyboardEvent<HTMLInputElement>
723 if (event.code.toLowerCase() === "enter") {
724 event.preventDefault();
726 this.handleAddInstance(key);
730 handleSaveSiteSubmit(i: SiteForm, event: any) {
731 event.preventDefault();
732 const auth = myAuthRequired();
733 i.setState(s => ((s.siteForm.auth = auth), s));
734 i.setState({ submitted: true });
736 const stateSiteForm = i.state.siteForm;
738 let form: EditSite | CreateSite;
740 if (i.props.siteRes.site_view.local_site.site_setup) {
741 form = stateSiteForm;
744 name: stateSiteForm.name ?? "My site",
745 sidebar: stateSiteForm.sidebar,
746 description: stateSiteForm.description,
747 icon: stateSiteForm.icon,
748 banner: stateSiteForm.banner,
749 community_creation_admin_only:
750 stateSiteForm.community_creation_admin_only,
751 enable_nsfw: stateSiteForm.enable_nsfw,
752 enable_downvotes: stateSiteForm.enable_downvotes,
753 application_question: stateSiteForm.application_question,
754 registration_mode: stateSiteForm.registration_mode,
755 require_email_verification: stateSiteForm.require_email_verification,
756 private_instance: stateSiteForm.private_instance,
757 default_theme: stateSiteForm.default_theme,
758 default_post_listing_type: stateSiteForm.default_post_listing_type,
759 application_email_admins: stateSiteForm.application_email_admins,
760 hide_modlog_mod_names: stateSiteForm.hide_modlog_mod_names,
761 legal_information: stateSiteForm.legal_information,
762 slur_filter_regex: stateSiteForm.slur_filter_regex,
763 actor_name_max_length: stateSiteForm.actor_name_max_length,
764 rate_limit_message: stateSiteForm.rate_limit_message,
765 rate_limit_message_per_second:
766 stateSiteForm.rate_limit_message_per_second,
767 rate_limit_comment: stateSiteForm.rate_limit_comment,
768 rate_limit_comment_per_second:
769 stateSiteForm.rate_limit_comment_per_second,
770 rate_limit_image: stateSiteForm.rate_limit_image,
771 rate_limit_image_per_second: stateSiteForm.rate_limit_image_per_second,
772 rate_limit_post: stateSiteForm.rate_limit_post,
773 rate_limit_post_per_second: stateSiteForm.rate_limit_post_per_second,
774 rate_limit_register: stateSiteForm.rate_limit_register,
775 rate_limit_register_per_second:
776 stateSiteForm.rate_limit_register_per_second,
777 rate_limit_search: stateSiteForm.rate_limit_search,
778 rate_limit_search_per_second:
779 stateSiteForm.rate_limit_search_per_second,
780 federation_enabled: stateSiteForm.federation_enabled,
781 federation_debug: stateSiteForm.federation_debug,
782 captcha_enabled: stateSiteForm.captcha_enabled,
783 captcha_difficulty: stateSiteForm.captcha_difficulty,
784 allowed_instances: stateSiteForm.allowed_instances,
785 blocked_instances: stateSiteForm.blocked_instances,
786 discussion_languages: stateSiteForm.discussion_languages,
791 i.props.onSaveSite(form);
794 handleAddInstance(key: InstanceKey) {
795 const instance = this.state.instance_select[key].trim();
797 if (!validInstanceTLD(instance)) {
801 if (!this.state.siteForm[key]?.includes(instance)) {
802 this.setState(s => ({
806 [key]: [...(s.siteForm[key] ?? []), instance],
809 ...s.instance_select,
814 const oppositeKey: InstanceKey =
815 key === "allowed_instances" ? "blocked_instances" : "allowed_instances";
816 if (this.state.siteForm[oppositeKey]?.includes(instance)) {
817 this.handleRemoveInstance({ key: oppositeKey, instance });
822 handleRemoveInstance({
829 this.setState(s => ({
833 [key]: s.siteForm[key]?.filter(i => i !== instance),
838 handleSiteNameChange(i: SiteForm, event: any) {
839 i.state.siteForm.name = event.target.value;
843 handleSiteSidebarChange(val: string) {
844 this.setState(s => ((s.siteForm.sidebar = val), s));
847 handleSiteLegalInfoChange(val: string) {
848 this.setState(s => ((s.siteForm.legal_information = val), s));
851 handleTaglineChange(i: SiteForm, index: number, val: string) {
852 const taglines = i.state.siteForm.taglines;
854 taglines[index] = val;
859 handleDeleteTaglineClick(
862 event: InfernoMouseEvent<HTMLButtonElement>
864 event.preventDefault();
865 const taglines = i.state.siteForm.taglines;
867 taglines.splice(index, 1);
868 i.state.siteForm.taglines = undefined;
870 i.state.siteForm.taglines = taglines;
875 handleAddTaglineClick(
877 event: InfernoMouseEvent<HTMLButtonElement>
879 event.preventDefault();
880 if (!i.state.siteForm.taglines) {
881 i.state.siteForm.taglines = [];
883 i.state.siteForm.taglines.push("");
887 handleSiteApplicationQuestionChange(val: string) {
888 this.setState(s => ((s.siteForm.application_question = val), s));
891 handleSiteDescChange(i: SiteForm, event: any) {
892 i.state.siteForm.description = event.target.value;
896 handleSiteEnableNsfwChange(i: SiteForm, event: any) {
897 i.state.siteForm.enable_nsfw = event.target.checked;
901 handleSiteRegistrationModeChange(i: SiteForm, event: any) {
902 i.state.siteForm.registration_mode = event.target.value;
906 handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) {
907 i.state.siteForm.community_creation_admin_only = event.target.checked;
911 handleSiteEnableDownvotesChange(i: SiteForm, event: any) {
912 i.state.siteForm.enable_downvotes = event.target.checked;
916 handleSiteRequireEmailVerification(i: SiteForm, event: any) {
917 i.state.siteForm.require_email_verification = event.target.checked;
921 handleSiteApplicationEmailAdmins(i: SiteForm, event: any) {
922 i.state.siteForm.application_email_admins = event.target.checked;
926 handleSiteReportsEmailAdmins(i: SiteForm, event: any) {
927 i.state.siteForm.reports_email_admins = event.target.checked;
931 handleSitePrivateInstance(i: SiteForm, event: any) {
932 i.state.siteForm.private_instance = event.target.checked;
936 handleSiteHideModlogModNames(i: SiteForm, event: any) {
937 i.state.siteForm.hide_modlog_mod_names = event.target.checked;
941 handleSiteDefaultTheme(i: SiteForm, event: any) {
942 i.state.siteForm.default_theme = event.target.value;
946 handleIconUpload(url: string) {
947 this.setState(s => ((s.siteForm.icon = url), s));
951 this.setState(s => ((s.siteForm.icon = ""), s));
954 handleBannerUpload(url: string) {
955 this.setState(s => ((s.siteForm.banner = url), s));
958 handleBannerRemove() {
959 this.setState(s => ((s.siteForm.banner = ""), s));
962 handleSiteSlurFilterRegex(i: SiteForm, event: any) {
963 i.setState(s => ((s.siteForm.slur_filter_regex = event.target.value), s));
966 handleSiteActorNameMaxLength(i: SiteForm, event: any) {
968 s => ((s.siteForm.actor_name_max_length = Number(event.target.value)), s)
972 handleSiteFederationEnabled(i: SiteForm, event: any) {
973 i.state.siteForm.federation_enabled = event.target.checked;
977 handleSiteFederationDebug(i: SiteForm, event: any) {
978 i.state.siteForm.federation_debug = event.target.checked;
982 handleSiteCaptchaEnabled(i: SiteForm, event: any) {
983 i.state.siteForm.captcha_enabled = event.target.checked;
987 handleSiteCaptchaDifficulty(i: SiteForm, event: any) {
988 i.setState(s => ((s.siteForm.captcha_difficulty = event.target.value), s));
991 handleDiscussionLanguageChange(val: number[]) {
992 this.setState(s => ((s.siteForm.discussion_languages = val), s));
995 handleDefaultPostListingTypeChange(val: ListingType) {
996 this.setState(s => ((s.siteForm.default_post_listing_type = val), s));