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