]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/community-form.tsx
fix: Adds ability to hide language warning
[lemmy-ui.git] / src / shared / components / community / community-form.tsx
1 import { Component, linkEvent } from "inferno";
2 import { Prompt } from "inferno-router";
3 import {
4   CommunityResponse,
5   CommunityView,
6   CreateCommunity,
7   EditCommunity,
8   Language,
9   UserOperation,
10   wsJsonToRes,
11   wsUserOp,
12 } from "lemmy-js-client";
13 import { Subscription } from "rxjs";
14 import { i18n } from "../../i18next";
15 import { UserService, WebSocketService } from "../../services";
16 import {
17   capitalizeFirstLetter,
18   myAuth,
19   randomStr,
20   wsClient,
21   wsSubscribe,
22 } from "../../utils";
23 import { Icon, Spinner } from "../common/icon";
24 import { ImageUploadForm } from "../common/image-upload-form";
25 import { LanguageSelect } from "../common/language-select";
26 import { MarkdownTextArea } from "../common/markdown-textarea";
27
28 interface CommunityFormProps {
29   community_view?: CommunityView; // If a community is given, that means this is an edit
30   allLanguages: Language[];
31   siteLanguages: number[];
32   communityLanguages?: number[];
33   onCancel?(): any;
34   onCreate?(community: CommunityView): any;
35   onEdit?(community: CommunityView): any;
36   enableNsfw?: boolean;
37 }
38
39 interface CommunityFormState {
40   form: {
41     name?: string;
42     title?: string;
43     description?: string;
44     icon?: string;
45     banner?: string;
46     nsfw?: boolean;
47     posting_restricted_to_mods?: boolean;
48     discussion_languages?: number[];
49   };
50   loading: boolean;
51 }
52
53 export class CommunityForm extends Component<
54   CommunityFormProps,
55   CommunityFormState
56 > {
57   private id = `community-form-${randomStr()}`;
58   private subscription?: Subscription;
59
60   state: CommunityFormState = {
61     form: {},
62     loading: false,
63   };
64
65   constructor(props: any, context: any) {
66     super(props, context);
67
68     this.handleCommunityDescriptionChange =
69       this.handleCommunityDescriptionChange.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.handleDiscussionLanguageChange =
78       this.handleDiscussionLanguageChange.bind(this);
79
80     this.parseMessage = this.parseMessage.bind(this);
81     this.subscription = wsSubscribe(this.parseMessage);
82     const cv = this.props.community_view;
83
84     if (cv) {
85       this.state = {
86         form: {
87           name: cv.community.name,
88           title: cv.community.title,
89           description: cv.community.description,
90           nsfw: cv.community.nsfw,
91           icon: cv.community.icon,
92           banner: cv.community.banner,
93           posting_restricted_to_mods: cv.community.posting_restricted_to_mods,
94           discussion_languages: this.props.communityLanguages,
95         },
96         loading: false,
97       };
98     }
99   }
100
101   componentDidUpdate() {
102     if (
103       !this.state.loading &&
104       (this.state.form.name ||
105         this.state.form.title ||
106         this.state.form.description)
107     ) {
108       window.onbeforeunload = () => true;
109     } else {
110       window.onbeforeunload = null;
111     }
112   }
113
114   componentWillUnmount() {
115     this.subscription?.unsubscribe();
116     window.onbeforeunload = null;
117   }
118
119   render() {
120     return (
121       <>
122         <Prompt
123           when={
124             !this.state.loading &&
125             (this.state.form.name ||
126               this.state.form.title ||
127               this.state.form.description)
128           }
129           message={i18n.t("block_leaving")}
130         />
131         <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
132           {!this.props.community_view && (
133             <div className="form-group row">
134               <label
135                 className="col-12 col-sm-2 col-form-label"
136                 htmlFor="community-name"
137               >
138                 {i18n.t("name")}
139                 <span
140                   className="position-absolute pointer unselectable ml-2 text-muted"
141                   data-tippy-content={i18n.t("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-name"
150                   className="form-control"
151                   value={this.state.form.name}
152                   onInput={linkEvent(this, this.handleCommunityNameChange)}
153                   required
154                   minLength={3}
155                   pattern="[a-z0-9_]+"
156                   title={i18n.t("community_reqs")}
157                 />
158               </div>
159             </div>
160           )}
161           <div className="form-group row">
162             <label
163               className="col-12 col-sm-2 col-form-label"
164               htmlFor="community-title"
165             >
166               {i18n.t("display_name")}
167               <span
168                 className="position-absolute pointer unselectable ml-2 text-muted"
169                 data-tippy-content={i18n.t("display_name_explain")}
170               >
171                 <Icon icon="help-circle" classes="icon-inline" />
172               </span>
173             </label>
174             <div className="col-12 col-sm-10">
175               <input
176                 type="text"
177                 id="community-title"
178                 value={this.state.form.title}
179                 onInput={linkEvent(this, this.handleCommunityTitleChange)}
180                 className="form-control"
181                 required
182                 minLength={3}
183                 maxLength={100}
184               />
185             </div>
186           </div>
187           <div className="form-group row">
188             <label className="col-12 col-sm-2">{i18n.t("icon")}</label>
189             <div className="col-12 col-sm-10">
190               <ImageUploadForm
191                 uploadTitle={i18n.t("upload_icon")}
192                 imageSrc={this.state.form.icon}
193                 onUpload={this.handleIconUpload}
194                 onRemove={this.handleIconRemove}
195                 rounded
196               />
197             </div>
198           </div>
199           <div className="form-group row">
200             <label className="col-12 col-sm-2">{i18n.t("banner")}</label>
201             <div className="col-12 col-sm-10">
202               <ImageUploadForm
203                 uploadTitle={i18n.t("upload_banner")}
204                 imageSrc={this.state.form.banner}
205                 onUpload={this.handleBannerUpload}
206                 onRemove={this.handleBannerRemove}
207               />
208             </div>
209           </div>
210           <div className="form-group row">
211             <label className="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
212               {i18n.t("sidebar")}
213             </label>
214             <div className="col-12 col-sm-10">
215               <MarkdownTextArea
216                 initialContent={this.state.form.description}
217                 placeholder={i18n.t("description")}
218                 onContentChange={this.handleCommunityDescriptionChange}
219                 allLanguages={[]}
220                 siteLanguages={[]}
221               />
222             </div>
223           </div>
224
225           {this.props.enableNsfw && (
226             <div className="form-group row">
227               <legend className="col-form-label col-sm-2 pt-0">
228                 {i18n.t("nsfw")}
229               </legend>
230               <div className="col-10">
231                 <div className="form-check">
232                   <input
233                     className="form-check-input position-static"
234                     id="community-nsfw"
235                     type="checkbox"
236                     checked={this.state.form.nsfw}
237                     onChange={linkEvent(this, this.handleCommunityNsfwChange)}
238                   />
239                 </div>
240               </div>
241             </div>
242           )}
243           <div className="form-group row">
244             <legend className="col-form-label col-6 pt-0">
245               {i18n.t("only_mods_can_post_in_community")}
246             </legend>
247             <div className="col-6">
248               <div className="form-check">
249                 <input
250                   className="form-check-input position-static"
251                   id="community-only-mods-can-post"
252                   type="checkbox"
253                   checked={this.state.form.posting_restricted_to_mods}
254                   onChange={linkEvent(
255                     this,
256                     this.handleCommunityPostingRestrictedToMods
257                   )}
258                 />
259               </div>
260             </div>
261           </div>
262           <LanguageSelect
263             allLanguages={this.props.allLanguages}
264             siteLanguages={this.props.siteLanguages}
265             showSite
266             selectedLanguageIds={this.state.form.discussion_languages}
267             multiple={true}
268             hideLanguageWarning={true}
269             onChange={this.handleDiscussionLanguageChange}
270           />
271           <div className="form-group row">
272             <div className="col-12">
273               <button
274                 type="submit"
275                 className="btn btn-secondary mr-2"
276                 disabled={this.state.loading}
277               >
278                 {this.state.loading ? (
279                   <Spinner />
280                 ) : this.props.community_view ? (
281                   capitalizeFirstLetter(i18n.t("save"))
282                 ) : (
283                   capitalizeFirstLetter(i18n.t("create"))
284                 )}
285               </button>
286               {this.props.community_view && (
287                 <button
288                   type="button"
289                   className="btn btn-secondary"
290                   onClick={linkEvent(this, this.handleCancel)}
291                 >
292                   {i18n.t("cancel")}
293                 </button>
294               )}
295             </div>
296           </div>
297         </form>
298       </>
299     );
300   }
301
302   handleCreateCommunitySubmit(i: CommunityForm, event: any) {
303     event.preventDefault();
304     i.setState({ loading: true });
305     const cForm = i.state.form;
306     const auth = myAuth();
307
308     const cv = i.props.community_view;
309
310     if (auth) {
311       if (cv) {
312         const form: EditCommunity = {
313           community_id: cv.community.id,
314           title: cForm.title,
315           description: cForm.description,
316           icon: cForm.icon,
317           banner: cForm.banner,
318           nsfw: cForm.nsfw,
319           posting_restricted_to_mods: cForm.posting_restricted_to_mods,
320           discussion_languages: cForm.discussion_languages,
321           auth,
322         };
323
324         WebSocketService.Instance.send(wsClient.editCommunity(form));
325       } else {
326         if (cForm.title && cForm.name) {
327           const form: CreateCommunity = {
328             name: cForm.name,
329             title: cForm.title,
330             description: cForm.description,
331             icon: cForm.icon,
332             banner: cForm.banner,
333             nsfw: cForm.nsfw,
334             posting_restricted_to_mods: cForm.posting_restricted_to_mods,
335             discussion_languages: cForm.discussion_languages,
336             auth,
337           };
338           WebSocketService.Instance.send(wsClient.createCommunity(form));
339         }
340       }
341     }
342     i.setState(i.state);
343   }
344
345   handleCommunityNameChange(i: CommunityForm, event: any) {
346     i.state.form.name = event.target.value;
347     i.setState(i.state);
348   }
349
350   handleCommunityTitleChange(i: CommunityForm, event: any) {
351     i.state.form.title = event.target.value;
352     i.setState(i.state);
353   }
354
355   handleCommunityDescriptionChange(val: string) {
356     this.setState(s => ((s.form.description = val), s));
357   }
358
359   handleCommunityNsfwChange(i: CommunityForm, event: any) {
360     i.state.form.nsfw = event.target.checked;
361     i.setState(i.state);
362   }
363
364   handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) {
365     i.state.form.posting_restricted_to_mods = event.target.checked;
366     i.setState(i.state);
367   }
368
369   handleCancel(i: CommunityForm) {
370     i.props.onCancel?.();
371   }
372
373   handleIconUpload(url: string) {
374     this.setState(s => ((s.form.icon = url), s));
375   }
376
377   handleIconRemove() {
378     this.setState(s => ((s.form.icon = ""), s));
379   }
380
381   handleBannerUpload(url: string) {
382     this.setState(s => ((s.form.banner = url), s));
383   }
384
385   handleBannerRemove() {
386     this.setState(s => ((s.form.banner = ""), s));
387   }
388
389   handleDiscussionLanguageChange(val: number[]) {
390     this.setState(s => ((s.form.discussion_languages = val), s));
391   }
392
393   parseMessage(msg: any) {
394     const op = wsUserOp(msg);
395     console.log(msg);
396     if (msg.error) {
397       // Errors handled by top level pages
398       // toast(i18n.t(msg.error), "danger");
399       this.setState({ loading: false });
400       return;
401     } else if (op == UserOperation.CreateCommunity) {
402       const data = wsJsonToRes<CommunityResponse>(msg);
403       this.props.onCreate?.(data.community_view);
404
405       // Update myUserInfo
406       const community = data.community_view.community;
407
408       const mui = UserService.Instance.myUserInfo;
409       if (mui) {
410         const person = mui.local_user_view.person;
411         mui.follows.push({
412           community,
413           follower: person,
414         });
415         mui.moderates.push({
416           community,
417           moderator: person,
418         });
419       }
420     } else if (op == UserOperation.EditCommunity) {
421       const data = wsJsonToRes<CommunityResponse>(msg);
422       this.setState({ loading: false });
423       this.props.onEdit?.(data.community_view);
424       const community = data.community_view.community;
425
426       const mui = UserService.Instance.myUserInfo;
427       if (mui) {
428         const followFound = mui.follows.findIndex(
429           f => f.community.id == community.id
430         );
431         if (followFound) {
432           mui.follows[followFound].community = community;
433         }
434
435         const moderatesFound = mui.moderates.findIndex(
436           f => f.community.id == community.id
437         );
438         if (moderatesFound) {
439           mui.moderates[moderatesFound].community = community;
440         }
441       }
442     }
443   }
444 }