]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/site-form.tsx
Adding mod / admin distinguish. (#744)
[lemmy-ui.git] / src / shared / components / home / site-form.tsx
1 import { None, Option, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import { Prompt } from "inferno-router";
4 import {
5   CreateSite,
6   EditSite,
7   ListingType,
8   Site,
9   toUndefined,
10 } from "lemmy-js-client";
11 import { i18n } from "../../i18next";
12 import { WebSocketService } from "../../services";
13 import {
14   auth,
15   capitalizeFirstLetter,
16   fetchThemeList,
17   wsClient,
18 } from "../../utils";
19 import { Spinner } from "../common/icon";
20 import { ImageUploadForm } from "../common/image-upload-form";
21 import { ListingTypeSelect } from "../common/listing-type-select";
22 import { MarkdownTextArea } from "../common/markdown-textarea";
23
24 interface SiteFormProps {
25   site: Option<Site>; // If a site is given, that means this is an edit
26   showLocal?: boolean;
27   onCancel?(): void;
28   onEdit?(): void;
29 }
30
31 interface SiteFormState {
32   siteForm: EditSite;
33   loading: boolean;
34   themeList: Option<string[]>;
35 }
36
37 export class SiteForm extends Component<SiteFormProps, SiteFormState> {
38   private emptyState: SiteFormState = {
39     siteForm: new EditSite({
40       enable_downvotes: Some(true),
41       open_registration: Some(true),
42       enable_nsfw: Some(true),
43       name: None,
44       icon: None,
45       banner: None,
46       require_email_verification: None,
47       require_application: None,
48       application_question: None,
49       private_instance: None,
50       default_theme: None,
51       sidebar: None,
52       default_post_listing_type: None,
53       legal_information: None,
54       description: None,
55       community_creation_admin_only: None,
56       auth: undefined,
57     }),
58     loading: false,
59     themeList: None,
60   };
61
62   constructor(props: any, context: any) {
63     super(props, context);
64
65     this.state = this.emptyState;
66     this.handleSiteSidebarChange = this.handleSiteSidebarChange.bind(this);
67     this.handleSiteLegalInfoChange = this.handleSiteLegalInfoChange.bind(this);
68     this.handleSiteApplicationQuestionChange =
69       this.handleSiteApplicationQuestionChange.bind(this);
70
71     this.handleIconUpload = this.handleIconUpload.bind(this);
72     this.handleIconRemove = this.handleIconRemove.bind(this);
73
74     this.handleBannerUpload = this.handleBannerUpload.bind(this);
75     this.handleBannerRemove = this.handleBannerRemove.bind(this);
76
77     this.handleDefaultPostListingTypeChange =
78       this.handleDefaultPostListingTypeChange.bind(this);
79
80     this.props.site.match({
81       some: site => {
82         this.state.siteForm = new EditSite({
83           name: Some(site.name),
84           sidebar: site.sidebar,
85           description: site.description,
86           enable_downvotes: Some(site.enable_downvotes),
87           open_registration: Some(site.open_registration),
88           enable_nsfw: Some(site.enable_nsfw),
89           community_creation_admin_only: Some(
90             site.community_creation_admin_only
91           ),
92           icon: site.icon,
93           banner: site.banner,
94           require_email_verification: Some(site.require_email_verification),
95           require_application: Some(site.require_application),
96           application_question: site.application_question,
97           private_instance: Some(site.private_instance),
98           default_theme: Some(site.default_theme),
99           default_post_listing_type: Some(site.default_post_listing_type),
100           legal_information: site.legal_information,
101           auth: undefined,
102         });
103       },
104       none: void 0,
105     });
106   }
107
108   async componentDidMount() {
109     this.state.themeList = Some(await fetchThemeList());
110     this.setState(this.state);
111   }
112
113   // Necessary to stop the loading
114   componentWillReceiveProps() {
115     this.state.loading = false;
116     this.setState(this.state);
117   }
118
119   componentDidUpdate() {
120     if (
121       !this.state.loading &&
122       this.props.site.isNone() &&
123       (this.state.siteForm.name ||
124         this.state.siteForm.sidebar ||
125         this.state.siteForm.application_question ||
126         this.state.siteForm.description)
127     ) {
128       window.onbeforeunload = () => true;
129     } else {
130       window.onbeforeunload = undefined;
131     }
132   }
133
134   componentWillUnmount() {
135     window.onbeforeunload = null;
136   }
137
138   render() {
139     return (
140       <>
141         <Prompt
142           when={
143             !this.state.loading &&
144             this.props.site.isNone() &&
145             (this.state.siteForm.name ||
146               this.state.siteForm.sidebar ||
147               this.state.siteForm.application_question ||
148               this.state.siteForm.description)
149           }
150           message={i18n.t("block_leaving")}
151         />
152         <form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}>
153           <h5>{`${
154             this.props.site.isSome()
155               ? capitalizeFirstLetter(i18n.t("save"))
156               : capitalizeFirstLetter(i18n.t("name"))
157           } ${i18n.t("your_site")}`}</h5>
158           <div class="form-group row">
159             <label class="col-12 col-form-label" htmlFor="create-site-name">
160               {i18n.t("name")}
161             </label>
162             <div class="col-12">
163               <input
164                 type="text"
165                 id="create-site-name"
166                 class="form-control"
167                 value={toUndefined(this.state.siteForm.name)}
168                 onInput={linkEvent(this, this.handleSiteNameChange)}
169                 required
170                 minLength={3}
171                 maxLength={20}
172               />
173             </div>
174           </div>
175           <div class="form-group">
176             <label>{i18n.t("icon")}</label>
177             <ImageUploadForm
178               uploadTitle={i18n.t("upload_icon")}
179               imageSrc={this.state.siteForm.icon}
180               onUpload={this.handleIconUpload}
181               onRemove={this.handleIconRemove}
182               rounded
183             />
184           </div>
185           <div class="form-group">
186             <label>{i18n.t("banner")}</label>
187             <ImageUploadForm
188               uploadTitle={i18n.t("upload_banner")}
189               imageSrc={this.state.siteForm.banner}
190               onUpload={this.handleBannerUpload}
191               onRemove={this.handleBannerRemove}
192             />
193           </div>
194           <div class="form-group row">
195             <label class="col-12 col-form-label" htmlFor="site-desc">
196               {i18n.t("description")}
197             </label>
198             <div class="col-12">
199               <input
200                 type="text"
201                 class="form-control"
202                 id="site-desc"
203                 value={toUndefined(this.state.siteForm.description)}
204                 onInput={linkEvent(this, this.handleSiteDescChange)}
205                 maxLength={150}
206               />
207             </div>
208           </div>
209           <div class="form-group row">
210             <label class="col-12 col-form-label">{i18n.t("sidebar")}</label>
211             <div class="col-12">
212               <MarkdownTextArea
213                 initialContent={this.state.siteForm.sidebar}
214                 placeholder={None}
215                 buttonTitle={None}
216                 maxLength={None}
217                 onContentChange={this.handleSiteSidebarChange}
218                 hideNavigationWarnings
219               />
220             </div>
221           </div>
222           <div class="form-group row">
223             <label class="col-12 col-form-label">
224               {i18n.t("legal_information")}
225             </label>
226             <div class="col-12">
227               <MarkdownTextArea
228                 initialContent={this.state.siteForm.legal_information}
229                 placeholder={None}
230                 buttonTitle={None}
231                 maxLength={None}
232                 onContentChange={this.handleSiteLegalInfoChange}
233                 hideNavigationWarnings
234               />
235             </div>
236           </div>
237           {this.state.siteForm.require_application.unwrapOr(false) && (
238             <div class="form-group row">
239               <label class="col-12 col-form-label">
240                 {i18n.t("application_questionnaire")}
241               </label>
242               <div class="col-12">
243                 <MarkdownTextArea
244                   initialContent={this.state.siteForm.application_question}
245                   placeholder={None}
246                   buttonTitle={None}
247                   maxLength={None}
248                   onContentChange={this.handleSiteApplicationQuestionChange}
249                   hideNavigationWarnings
250                 />
251               </div>
252             </div>
253           )}
254           <div class="form-group row">
255             <div class="col-12">
256               <div class="form-check">
257                 <input
258                   class="form-check-input"
259                   id="create-site-downvotes"
260                   type="checkbox"
261                   checked={toUndefined(this.state.siteForm.enable_downvotes)}
262                   onChange={linkEvent(
263                     this,
264                     this.handleSiteEnableDownvotesChange
265                   )}
266                 />
267                 <label class="form-check-label" htmlFor="create-site-downvotes">
268                   {i18n.t("enable_downvotes")}
269                 </label>
270               </div>
271             </div>
272           </div>
273           <div class="form-group row">
274             <div class="col-12">
275               <div class="form-check">
276                 <input
277                   class="form-check-input"
278                   id="create-site-enable-nsfw"
279                   type="checkbox"
280                   checked={toUndefined(this.state.siteForm.enable_nsfw)}
281                   onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
282                 />
283                 <label
284                   class="form-check-label"
285                   htmlFor="create-site-enable-nsfw"
286                 >
287                   {i18n.t("enable_nsfw")}
288                 </label>
289               </div>
290             </div>
291           </div>
292           <div class="form-group row">
293             <div class="col-12">
294               <div class="form-check">
295                 <input
296                   class="form-check-input"
297                   id="create-site-open-registration"
298                   type="checkbox"
299                   checked={toUndefined(this.state.siteForm.open_registration)}
300                   onChange={linkEvent(
301                     this,
302                     this.handleSiteOpenRegistrationChange
303                   )}
304                 />
305                 <label
306                   class="form-check-label"
307                   htmlFor="create-site-open-registration"
308                 >
309                   {i18n.t("open_registration")}
310                 </label>
311               </div>
312             </div>
313           </div>
314           <div class="form-group row">
315             <div class="col-12">
316               <div class="form-check">
317                 <input
318                   class="form-check-input"
319                   id="create-site-community-creation-admin-only"
320                   type="checkbox"
321                   checked={toUndefined(
322                     this.state.siteForm.community_creation_admin_only
323                   )}
324                   onChange={linkEvent(
325                     this,
326                     this.handleSiteCommunityCreationAdminOnly
327                   )}
328                 />
329                 <label
330                   class="form-check-label"
331                   htmlFor="create-site-community-creation-admin-only"
332                 >
333                   {i18n.t("community_creation_admin_only")}
334                 </label>
335               </div>
336             </div>
337           </div>
338           <div class="form-group row">
339             <div class="col-12">
340               <div class="form-check">
341                 <input
342                   class="form-check-input"
343                   id="create-site-require-email-verification"
344                   type="checkbox"
345                   checked={toUndefined(
346                     this.state.siteForm.require_email_verification
347                   )}
348                   onChange={linkEvent(
349                     this,
350                     this.handleSiteRequireEmailVerification
351                   )}
352                 />
353                 <label
354                   class="form-check-label"
355                   htmlFor="create-site-require-email-verification"
356                 >
357                   {i18n.t("require_email_verification")}
358                 </label>
359               </div>
360             </div>
361           </div>
362           <div class="form-group row">
363             <div class="col-12">
364               <div class="form-check">
365                 <input
366                   class="form-check-input"
367                   id="create-site-require-application"
368                   type="checkbox"
369                   checked={toUndefined(this.state.siteForm.require_application)}
370                   onChange={linkEvent(this, this.handleSiteRequireApplication)}
371                 />
372                 <label
373                   class="form-check-label"
374                   htmlFor="create-site-require-application"
375                 >
376                   {i18n.t("require_registration_application")}
377                 </label>
378               </div>
379             </div>
380           </div>
381           <div class="form-group row">
382             <div class="col-12">
383               <label
384                 class="form-check-label mr-2"
385                 htmlFor="create-site-default-theme"
386               >
387                 {i18n.t("theme")}
388               </label>
389               <select
390                 id="create-site-default-theme"
391                 value={toUndefined(this.state.siteForm.default_theme)}
392                 onChange={linkEvent(this, this.handleSiteDefaultTheme)}
393                 class="custom-select w-auto"
394               >
395                 <option value="browser">{i18n.t("browser_default")}</option>
396                 {this.state.themeList.unwrapOr([]).map(theme => (
397                   <option value={theme}>{theme}</option>
398                 ))}
399               </select>
400             </div>
401           </div>
402           {this.props.showLocal && (
403             <form className="form-group row">
404               <label class="col-sm-3">{i18n.t("listing_type")}</label>
405               <div class="col-sm-9">
406                 <ListingTypeSelect
407                   type_={
408                     ListingType[
409                       this.state.siteForm.default_post_listing_type.unwrapOr(
410                         "Local"
411                       )
412                     ]
413                   }
414                   showLocal
415                   showSubscribed={false}
416                   onChange={this.handleDefaultPostListingTypeChange}
417                 />
418               </div>
419             </form>
420           )}
421           <div class="form-group row">
422             <div class="col-12">
423               <div class="form-check">
424                 <input
425                   class="form-check-input"
426                   id="create-site-private-instance"
427                   type="checkbox"
428                   value={toUndefined(this.state.siteForm.default_theme)}
429                   onChange={linkEvent(this, this.handleSitePrivateInstance)}
430                 />
431                 <label
432                   class="form-check-label"
433                   htmlFor="create-site-private-instance"
434                 >
435                   {i18n.t("private_instance")}
436                 </label>
437               </div>
438             </div>
439           </div>
440           <div class="form-group row">
441             <div class="col-12">
442               <button
443                 type="submit"
444                 class="btn btn-secondary mr-2"
445                 disabled={this.state.loading}
446               >
447                 {this.state.loading ? (
448                   <Spinner />
449                 ) : this.props.site.isSome() ? (
450                   capitalizeFirstLetter(i18n.t("save"))
451                 ) : (
452                   capitalizeFirstLetter(i18n.t("create"))
453                 )}
454               </button>
455               {this.props.site.isSome() && (
456                 <button
457                   type="button"
458                   class="btn btn-secondary"
459                   onClick={linkEvent(this, this.handleCancel)}
460                 >
461                   {i18n.t("cancel")}
462                 </button>
463               )}
464             </div>
465           </div>
466         </form>
467       </>
468     );
469   }
470
471   handleCreateSiteSubmit(i: SiteForm, event: any) {
472     event.preventDefault();
473     i.state.loading = true;
474     i.state.siteForm.auth = auth().unwrap();
475
476     if (i.props.site.isSome()) {
477       WebSocketService.Instance.send(wsClient.editSite(i.state.siteForm));
478       i.props.onEdit();
479     } else {
480       let sForm = i.state.siteForm;
481       let form = new CreateSite({
482         name: sForm.name.unwrapOr("My site"),
483         sidebar: sForm.sidebar,
484         description: sForm.description,
485         icon: sForm.icon,
486         banner: sForm.banner,
487         community_creation_admin_only: sForm.community_creation_admin_only,
488         enable_nsfw: sForm.enable_nsfw,
489         enable_downvotes: sForm.enable_downvotes,
490         require_application: sForm.require_application,
491         application_question: sForm.application_question,
492         open_registration: sForm.open_registration,
493         require_email_verification: sForm.require_email_verification,
494         private_instance: sForm.private_instance,
495         default_theme: sForm.default_theme,
496         default_post_listing_type: sForm.default_post_listing_type,
497         auth: auth().unwrap(),
498       });
499       WebSocketService.Instance.send(wsClient.createSite(form));
500     }
501     i.setState(i.state);
502   }
503
504   handleSiteNameChange(i: SiteForm, event: any) {
505     i.state.siteForm.name = Some(event.target.value);
506     i.setState(i.state);
507   }
508
509   handleSiteSidebarChange(val: string) {
510     this.state.siteForm.sidebar = Some(val);
511     this.setState(this.state);
512   }
513
514   handleSiteLegalInfoChange(val: string) {
515     this.state.siteForm.legal_information = Some(val);
516     this.setState(this.state);
517   }
518
519   handleSiteApplicationQuestionChange(val: string) {
520     this.state.siteForm.application_question = Some(val);
521     this.setState(this.state);
522   }
523
524   handleSiteDescChange(i: SiteForm, event: any) {
525     i.state.siteForm.description = Some(event.target.value);
526     i.setState(i.state);
527   }
528
529   handleSiteEnableNsfwChange(i: SiteForm, event: any) {
530     i.state.siteForm.enable_nsfw = Some(event.target.checked);
531     i.setState(i.state);
532   }
533
534   handleSiteOpenRegistrationChange(i: SiteForm, event: any) {
535     i.state.siteForm.open_registration = Some(event.target.checked);
536     i.setState(i.state);
537   }
538
539   handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) {
540     i.state.siteForm.community_creation_admin_only = Some(event.target.checked);
541     i.setState(i.state);
542   }
543
544   handleSiteEnableDownvotesChange(i: SiteForm, event: any) {
545     i.state.siteForm.enable_downvotes = Some(event.target.checked);
546     i.setState(i.state);
547   }
548
549   handleSiteRequireApplication(i: SiteForm, event: any) {
550     i.state.siteForm.require_application = Some(event.target.checked);
551     i.setState(i.state);
552   }
553
554   handleSiteRequireEmailVerification(i: SiteForm, event: any) {
555     i.state.siteForm.require_email_verification = Some(event.target.checked);
556     i.setState(i.state);
557   }
558
559   handleSitePrivateInstance(i: SiteForm, event: any) {
560     i.state.siteForm.private_instance = Some(event.target.checked);
561     i.setState(i.state);
562   }
563
564   handleSiteDefaultTheme(i: SiteForm, event: any) {
565     i.state.siteForm.default_theme = Some(event.target.value);
566     i.setState(i.state);
567   }
568
569   handleCancel(i: SiteForm) {
570     i.props.onCancel();
571   }
572
573   handleIconUpload(url: string) {
574     this.state.siteForm.icon = Some(url);
575     this.setState(this.state);
576   }
577
578   handleIconRemove() {
579     this.state.siteForm.icon = Some("");
580     this.setState(this.state);
581   }
582
583   handleBannerUpload(url: string) {
584     this.state.siteForm.banner = Some(url);
585     this.setState(this.state);
586   }
587
588   handleBannerRemove() {
589     this.state.siteForm.banner = Some("");
590     this.setState(this.state);
591   }
592
593   handleDefaultPostListingTypeChange(val: ListingType) {
594     this.state.siteForm.default_post_listing_type = Some(
595       ListingType[ListingType[val]]
596     );
597     this.setState(this.state);
598   }
599 }