]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/community-form.tsx
component classes v2
[lemmy-ui.git] / src / shared / components / community / community-form.tsx
1 import { Component, linkEvent } from "inferno";
2 import {
3   CommunityView,
4   CreateCommunity,
5   EditCommunity,
6   Language,
7 } from "lemmy-js-client";
8 import { i18n } from "../../i18next";
9 import { capitalizeFirstLetter, myAuthRequired, randomStr } from "../../utils";
10 import { Icon, Spinner } from "../common/icon";
11 import { ImageUploadForm } from "../common/image-upload-form";
12 import { LanguageSelect } from "../common/language-select";
13 import { MarkdownTextArea } from "../common/markdown-textarea";
14 import NavigationPrompt from "../common/navigation-prompt";
15
16 interface CommunityFormProps {
17   community_view?: CommunityView; // If a community is given, that means this is an edit
18   allLanguages: Language[];
19   siteLanguages: number[];
20   communityLanguages?: number[];
21   onCancel?(): any;
22   onUpsertCommunity(form: CreateCommunity | EditCommunity): void;
23   enableNsfw?: boolean;
24   loading?: boolean;
25 }
26
27 interface CommunityFormState {
28   form: {
29     name?: string;
30     title?: string;
31     description?: string;
32     icon?: string;
33     banner?: string;
34     nsfw?: boolean;
35     posting_restricted_to_mods?: boolean;
36     discussion_languages?: number[];
37   };
38   submitted: boolean;
39 }
40
41 export class CommunityForm extends Component<
42   CommunityFormProps,
43   CommunityFormState
44 > {
45   private id = `community-form-${randomStr()}`;
46
47   state: CommunityFormState = {
48     form: {},
49     submitted: false,
50   };
51
52   constructor(props: any, context: any) {
53     super(props, context);
54
55     this.handleCommunityDescriptionChange =
56       this.handleCommunityDescriptionChange.bind(this);
57
58     this.handleIconUpload = this.handleIconUpload.bind(this);
59     this.handleIconRemove = this.handleIconRemove.bind(this);
60
61     this.handleBannerUpload = this.handleBannerUpload.bind(this);
62     this.handleBannerRemove = this.handleBannerRemove.bind(this);
63
64     this.handleDiscussionLanguageChange =
65       this.handleDiscussionLanguageChange.bind(this);
66
67     const cv = this.props.community_view;
68
69     if (cv) {
70       this.state = {
71         ...this.state,
72         form: {
73           name: cv.community.name,
74           title: cv.community.title,
75           description: cv.community.description,
76           nsfw: cv.community.nsfw,
77           icon: cv.community.icon,
78           banner: cv.community.banner,
79           posting_restricted_to_mods: cv.community.posting_restricted_to_mods,
80           discussion_languages: this.props.communityLanguages,
81         },
82       };
83     }
84   }
85
86   render() {
87     return (
88       <form
89         className="community-form"
90         onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}
91       >
92         <NavigationPrompt
93           when={
94             !this.props.loading &&
95             !!(
96               this.state.form.name ||
97               this.state.form.title ||
98               this.state.form.description
99             ) &&
100             !this.state.submitted
101           }
102         />
103         {!this.props.community_view && (
104           <div className="mb-3 row">
105             <label
106               className="col-12 col-sm-2 col-form-label"
107               htmlFor="community-name"
108             >
109               {i18n.t("name")}
110               <span
111                 className="position-absolute pointer unselectable ms-2 text-muted"
112                 data-tippy-content={i18n.t("name_explain")}
113               >
114                 <Icon icon="help-circle" classes="icon-inline" />
115               </span>
116             </label>
117             <div className="col-12 col-sm-10">
118               <input
119                 type="text"
120                 id="community-name"
121                 className="form-control"
122                 value={this.state.form.name}
123                 onInput={linkEvent(this, this.handleCommunityNameChange)}
124                 required
125                 minLength={3}
126                 pattern="[a-z0-9_]+"
127                 title={i18n.t("community_reqs")}
128               />
129             </div>
130           </div>
131         )}
132         <div className="mb-3 row">
133           <label
134             className="col-12 col-sm-2 col-form-label"
135             htmlFor="community-title"
136           >
137             {i18n.t("display_name")}
138             <span
139               className="position-absolute pointer unselectable ms-2 text-muted"
140               data-tippy-content={i18n.t("display_name_explain")}
141             >
142               <Icon icon="help-circle" classes="icon-inline" />
143             </span>
144           </label>
145           <div className="col-12 col-sm-10">
146             <input
147               type="text"
148               id="community-title"
149               value={this.state.form.title}
150               onInput={linkEvent(this, this.handleCommunityTitleChange)}
151               className="form-control"
152               required
153               minLength={3}
154               maxLength={100}
155             />
156           </div>
157         </div>
158         <div className="mb-3 row">
159           <label className="col-12 col-sm-2 col-form-label">
160             {i18n.t("icon")}
161           </label>
162           <div className="col-12 col-sm-10">
163             <ImageUploadForm
164               uploadTitle={i18n.t("upload_icon")}
165               imageSrc={this.state.form.icon}
166               onUpload={this.handleIconUpload}
167               onRemove={this.handleIconRemove}
168               rounded
169             />
170           </div>
171         </div>
172         <div className="mb-3 row">
173           <label className="col-12 col-sm-2 col-form-label">
174             {i18n.t("banner")}
175           </label>
176           <div className="col-12 col-sm-10">
177             <ImageUploadForm
178               uploadTitle={i18n.t("upload_banner")}
179               imageSrc={this.state.form.banner}
180               onUpload={this.handleBannerUpload}
181               onRemove={this.handleBannerRemove}
182             />
183           </div>
184         </div>
185         <div className="mb-3 row">
186           <label className="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
187             {i18n.t("sidebar")}
188           </label>
189           <div className="col-12 col-sm-10">
190             <MarkdownTextArea
191               initialContent={this.state.form.description}
192               placeholder={i18n.t("description")}
193               onContentChange={this.handleCommunityDescriptionChange}
194               hideNavigationWarnings
195               allLanguages={[]}
196               siteLanguages={[]}
197             />
198           </div>
199         </div>
200
201         {this.props.enableNsfw && (
202           <div className="mb-3 row">
203             <legend className="col-form-label col-sm-2 pt-0">
204               {i18n.t("nsfw")}
205             </legend>
206             <div className="col-10">
207               <div className="form-check">
208                 <input
209                   className="form-check-input position-static"
210                   id="community-nsfw"
211                   type="checkbox"
212                   checked={this.state.form.nsfw}
213                   onChange={linkEvent(this, this.handleCommunityNsfwChange)}
214                 />
215               </div>
216             </div>
217           </div>
218         )}
219         <div className="mb-3 row">
220           <legend className="col-form-label col-6 pt-0">
221             {i18n.t("only_mods_can_post_in_community")}
222           </legend>
223           <div className="col-6">
224             <div className="form-check">
225               <input
226                 className="form-check-input position-static"
227                 id="community-only-mods-can-post"
228                 type="checkbox"
229                 checked={this.state.form.posting_restricted_to_mods}
230                 onChange={linkEvent(
231                   this,
232                   this.handleCommunityPostingRestrictedToMods
233                 )}
234               />
235             </div>
236           </div>
237         </div>
238         <LanguageSelect
239           allLanguages={this.props.allLanguages}
240           siteLanguages={this.props.siteLanguages}
241           showSite
242           selectedLanguageIds={this.state.form.discussion_languages}
243           multiple={true}
244           onChange={this.handleDiscussionLanguageChange}
245         />
246         <div className="mb-3 row">
247           <div className="col-12">
248             <button
249               type="submit"
250               className="btn btn-secondary me-2"
251               disabled={this.props.loading}
252             >
253               {this.props.loading ? (
254                 <Spinner />
255               ) : this.props.community_view ? (
256                 capitalizeFirstLetter(i18n.t("save"))
257               ) : (
258                 capitalizeFirstLetter(i18n.t("create"))
259               )}
260             </button>
261             {this.props.community_view && (
262               <button
263                 type="button"
264                 className="btn btn-secondary"
265                 onClick={linkEvent(this, this.handleCancel)}
266               >
267                 {i18n.t("cancel")}
268               </button>
269             )}
270           </div>
271         </div>
272       </form>
273     );
274   }
275
276   handleCreateCommunitySubmit(i: CommunityForm, event: any) {
277     event.preventDefault();
278     i.setState({ submitted: true });
279     const cForm = i.state.form;
280     const auth = myAuthRequired();
281
282     const cv = i.props.community_view;
283
284     if (cv) {
285       i.props.onUpsertCommunity({
286         community_id: cv.community.id,
287         title: cForm.title,
288         description: cForm.description,
289         icon: cForm.icon,
290         banner: cForm.banner,
291         nsfw: cForm.nsfw,
292         posting_restricted_to_mods: cForm.posting_restricted_to_mods,
293         discussion_languages: cForm.discussion_languages,
294         auth,
295       });
296     } else {
297       if (cForm.title && cForm.name) {
298         i.props.onUpsertCommunity({
299           name: cForm.name,
300           title: cForm.title,
301           description: cForm.description,
302           icon: cForm.icon,
303           banner: cForm.banner,
304           nsfw: cForm.nsfw,
305           posting_restricted_to_mods: cForm.posting_restricted_to_mods,
306           discussion_languages: cForm.discussion_languages,
307           auth,
308         });
309       }
310     }
311   }
312
313   handleCommunityNameChange(i: CommunityForm, event: any) {
314     i.setState(s => ((s.form.name = event.target.value), s));
315   }
316
317   handleCommunityTitleChange(i: CommunityForm, event: any) {
318     i.setState(s => ((s.form.title = event.target.value), s));
319   }
320
321   handleCommunityDescriptionChange(val: string) {
322     this.setState(s => ((s.form.description = val), s));
323   }
324
325   handleCommunityNsfwChange(i: CommunityForm, event: any) {
326     i.setState(s => ((s.form.nsfw = event.target.checked), s));
327   }
328
329   handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) {
330     i.setState(
331       s => ((s.form.posting_restricted_to_mods = event.target.checked), s)
332     );
333   }
334
335   handleCancel(i: CommunityForm) {
336     i.props.onCancel?.();
337   }
338
339   handleIconUpload(url: string) {
340     this.setState(s => ((s.form.icon = url), s));
341   }
342
343   handleIconRemove() {
344     this.setState(s => ((s.form.icon = ""), s));
345   }
346
347   handleBannerUpload(url: string) {
348     this.setState(s => ((s.form.banner = url), s));
349   }
350
351   handleBannerRemove() {
352     this.setState(s => ((s.form.banner = ""), s));
353   }
354
355   handleDiscussionLanguageChange(val: number[]) {
356     this.setState(s => ((s.form.discussion_languages = val), s));
357   }
358 }