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