]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/community-form.tsx
feat: Bootstrap 5 (#1378)
[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 onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
89         <NavigationPrompt
90           when={
91             !this.props.loading &&
92             !!(
93               this.state.form.name ||
94               this.state.form.title ||
95               this.state.form.description
96             ) &&
97             !this.state.submitted
98           }
99         />
100         {!this.props.community_view && (
101           <div className="mb-3 row">
102             <label
103               className="col-12 col-sm-2 col-form-label"
104               htmlFor="community-name"
105             >
106               {i18n.t("name")}
107               <span
108                 className="position-absolute pointer unselectable ms-2 text-muted"
109                 data-tippy-content={i18n.t("name_explain")}
110               >
111                 <Icon icon="help-circle" classes="icon-inline" />
112               </span>
113             </label>
114             <div className="col-12 col-sm-10">
115               <input
116                 type="text"
117                 id="community-name"
118                 className="form-control"
119                 value={this.state.form.name}
120                 onInput={linkEvent(this, this.handleCommunityNameChange)}
121                 required
122                 minLength={3}
123                 pattern="[a-z0-9_]+"
124                 title={i18n.t("community_reqs")}
125               />
126             </div>
127           </div>
128         )}
129         <div className="mb-3 row">
130           <label
131             className="col-12 col-sm-2 col-form-label"
132             htmlFor="community-title"
133           >
134             {i18n.t("display_name")}
135             <span
136               className="position-absolute pointer unselectable ms-2 text-muted"
137               data-tippy-content={i18n.t("display_name_explain")}
138             >
139               <Icon icon="help-circle" classes="icon-inline" />
140             </span>
141           </label>
142           <div className="col-12 col-sm-10">
143             <input
144               type="text"
145               id="community-title"
146               value={this.state.form.title}
147               onInput={linkEvent(this, this.handleCommunityTitleChange)}
148               className="form-control"
149               required
150               minLength={3}
151               maxLength={100}
152             />
153           </div>
154         </div>
155         <div className="mb-3 row">
156           <label className="col-12 col-sm-2 col-form-label">
157             {i18n.t("icon")}
158           </label>
159           <div className="col-12 col-sm-10">
160             <ImageUploadForm
161               uploadTitle={i18n.t("upload_icon")}
162               imageSrc={this.state.form.icon}
163               onUpload={this.handleIconUpload}
164               onRemove={this.handleIconRemove}
165               rounded
166             />
167           </div>
168         </div>
169         <div className="mb-3 row">
170           <label className="col-12 col-sm-2 col-form-label">
171             {i18n.t("banner")}
172           </label>
173           <div className="col-12 col-sm-10">
174             <ImageUploadForm
175               uploadTitle={i18n.t("upload_banner")}
176               imageSrc={this.state.form.banner}
177               onUpload={this.handleBannerUpload}
178               onRemove={this.handleBannerRemove}
179             />
180           </div>
181         </div>
182         <div className="mb-3 row">
183           <label className="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
184             {i18n.t("sidebar")}
185           </label>
186           <div className="col-12 col-sm-10">
187             <MarkdownTextArea
188               initialContent={this.state.form.description}
189               placeholder={i18n.t("description")}
190               onContentChange={this.handleCommunityDescriptionChange}
191               hideNavigationWarnings
192               allLanguages={[]}
193               siteLanguages={[]}
194             />
195           </div>
196         </div>
197
198         {this.props.enableNsfw && (
199           <div className="mb-3 row">
200             <legend className="col-form-label col-sm-2 pt-0">
201               {i18n.t("nsfw")}
202             </legend>
203             <div className="col-10">
204               <div className="form-check">
205                 <input
206                   className="form-check-input position-static"
207                   id="community-nsfw"
208                   type="checkbox"
209                   checked={this.state.form.nsfw}
210                   onChange={linkEvent(this, this.handleCommunityNsfwChange)}
211                 />
212               </div>
213             </div>
214           </div>
215         )}
216         <div className="mb-3 row">
217           <legend className="col-form-label col-6 pt-0">
218             {i18n.t("only_mods_can_post_in_community")}
219           </legend>
220           <div className="col-6">
221             <div className="form-check">
222               <input
223                 className="form-check-input position-static"
224                 id="community-only-mods-can-post"
225                 type="checkbox"
226                 checked={this.state.form.posting_restricted_to_mods}
227                 onChange={linkEvent(
228                   this,
229                   this.handleCommunityPostingRestrictedToMods
230                 )}
231               />
232             </div>
233           </div>
234         </div>
235         <LanguageSelect
236           allLanguages={this.props.allLanguages}
237           siteLanguages={this.props.siteLanguages}
238           showSite
239           selectedLanguageIds={this.state.form.discussion_languages}
240           multiple={true}
241           onChange={this.handleDiscussionLanguageChange}
242         />
243         <div className="mb-3 row">
244           <div className="col-12">
245             <button
246               type="submit"
247               className="btn btn-secondary me-2"
248               disabled={this.props.loading}
249             >
250               {this.props.loading ? (
251                 <Spinner />
252               ) : this.props.community_view ? (
253                 capitalizeFirstLetter(i18n.t("save"))
254               ) : (
255                 capitalizeFirstLetter(i18n.t("create"))
256               )}
257             </button>
258             {this.props.community_view && (
259               <button
260                 type="button"
261                 className="btn btn-secondary"
262                 onClick={linkEvent(this, this.handleCancel)}
263               >
264                 {i18n.t("cancel")}
265               </button>
266             )}
267           </div>
268         </div>
269       </form>
270     );
271   }
272
273   handleCreateCommunitySubmit(i: CommunityForm, event: any) {
274     event.preventDefault();
275     i.setState({ submitted: true });
276     const cForm = i.state.form;
277     const auth = myAuthRequired();
278
279     const cv = i.props.community_view;
280
281     if (cv) {
282       i.props.onUpsertCommunity({
283         community_id: cv.community.id,
284         title: cForm.title,
285         description: cForm.description,
286         icon: cForm.icon,
287         banner: cForm.banner,
288         nsfw: cForm.nsfw,
289         posting_restricted_to_mods: cForm.posting_restricted_to_mods,
290         discussion_languages: cForm.discussion_languages,
291         auth,
292       });
293     } else {
294       if (cForm.title && cForm.name) {
295         i.props.onUpsertCommunity({
296           name: cForm.name,
297           title: cForm.title,
298           description: cForm.description,
299           icon: cForm.icon,
300           banner: cForm.banner,
301           nsfw: cForm.nsfw,
302           posting_restricted_to_mods: cForm.posting_restricted_to_mods,
303           discussion_languages: cForm.discussion_languages,
304           auth,
305         });
306       }
307     }
308   }
309
310   handleCommunityNameChange(i: CommunityForm, event: any) {
311     i.setState(s => ((s.form.name = event.target.value), s));
312   }
313
314   handleCommunityTitleChange(i: CommunityForm, event: any) {
315     i.setState(s => ((s.form.title = event.target.value), s));
316   }
317
318   handleCommunityDescriptionChange(val: string) {
319     this.setState(s => ((s.form.description = val), s));
320   }
321
322   handleCommunityNsfwChange(i: CommunityForm, event: any) {
323     i.setState(s => ((s.form.nsfw = event.target.checked), s));
324   }
325
326   handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) {
327     i.setState(
328       s => ((s.form.posting_restricted_to_mods = event.target.checked), s)
329     );
330   }
331
332   handleCancel(i: CommunityForm) {
333     i.props.onCancel?.();
334   }
335
336   handleIconUpload(url: string) {
337     this.setState(s => ((s.form.icon = url), s));
338   }
339
340   handleIconRemove() {
341     this.setState(s => ((s.form.icon = ""), s));
342   }
343
344   handleBannerUpload(url: string) {
345     this.setState(s => ((s.form.banner = url), s));
346   }
347
348   handleBannerRemove() {
349     this.setState(s => ((s.form.banner = ""), s));
350   }
351
352   handleDiscussionLanguageChange(val: number[]) {
353     this.setState(s => ((s.form.discussion_languages = val), s));
354   }
355 }