]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/community-form.tsx
Fix prompt component (#1223)
[lemmy-ui.git] / src / shared / components / community / community-form.tsx
1 import { Component, linkEvent } from "inferno";
2 import {
3   CommunityResponse,
4   CommunityView,
5   CreateCommunity,
6   EditCommunity,
7   Language,
8   UserOperation,
9   wsJsonToRes,
10   wsUserOp,
11 } from "lemmy-js-client";
12 import { Subscription } from "rxjs";
13 import { i18n } from "../../i18next";
14 import { UserService, WebSocketService } from "../../services";
15 import {
16   capitalizeFirstLetter,
17   myAuth,
18   randomStr,
19   wsClient,
20   wsSubscribe,
21 } from "../../utils";
22 import { Icon, Spinner } from "../common/icon";
23 import { ImageUploadForm } from "../common/image-upload-form";
24 import { LanguageSelect } from "../common/language-select";
25 import { MarkdownTextArea } from "../common/markdown-textarea";
26 import NavigationPrompt from "../common/navigation-prompt";
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         <NavigationPrompt
123           when={
124             !this.state.loading &&
125             !!(
126               this.state.form.name ||
127               this.state.form.title ||
128               this.state.form.description
129             )
130           }
131         />
132         <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
133           {!this.props.community_view && (
134             <div className="form-group row">
135               <label
136                 className="col-12 col-sm-2 col-form-label"
137                 htmlFor="community-name"
138               >
139                 {i18n.t("name")}
140                 <span
141                   className="position-absolute pointer unselectable ml-2 text-muted"
142                   data-tippy-content={i18n.t("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-name"
151                   className="form-control"
152                   value={this.state.form.name}
153                   onInput={linkEvent(this, this.handleCommunityNameChange)}
154                   required
155                   minLength={3}
156                   pattern="[a-z0-9_]+"
157                   title={i18n.t("community_reqs")}
158                 />
159               </div>
160             </div>
161           )}
162           <div className="form-group row">
163             <label
164               className="col-12 col-sm-2 col-form-label"
165               htmlFor="community-title"
166             >
167               {i18n.t("display_name")}
168               <span
169                 className="position-absolute pointer unselectable ml-2 text-muted"
170                 data-tippy-content={i18n.t("display_name_explain")}
171               >
172                 <Icon icon="help-circle" classes="icon-inline" />
173               </span>
174             </label>
175             <div className="col-12 col-sm-10">
176               <input
177                 type="text"
178                 id="community-title"
179                 value={this.state.form.title}
180                 onInput={linkEvent(this, this.handleCommunityTitleChange)}
181                 className="form-control"
182                 required
183                 minLength={3}
184                 maxLength={100}
185               />
186             </div>
187           </div>
188           <div className="form-group row">
189             <label className="col-12 col-sm-2">{i18n.t("icon")}</label>
190             <div className="col-12 col-sm-10">
191               <ImageUploadForm
192                 uploadTitle={i18n.t("upload_icon")}
193                 imageSrc={this.state.form.icon}
194                 onUpload={this.handleIconUpload}
195                 onRemove={this.handleIconRemove}
196                 rounded
197               />
198             </div>
199           </div>
200           <div className="form-group row">
201             <label className="col-12 col-sm-2">{i18n.t("banner")}</label>
202             <div className="col-12 col-sm-10">
203               <ImageUploadForm
204                 uploadTitle={i18n.t("upload_banner")}
205                 imageSrc={this.state.form.banner}
206                 onUpload={this.handleBannerUpload}
207                 onRemove={this.handleBannerRemove}
208               />
209             </div>
210           </div>
211           <div className="form-group row">
212             <label className="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
213               {i18n.t("sidebar")}
214             </label>
215             <div className="col-12 col-sm-10">
216               <MarkdownTextArea
217                 initialContent={this.state.form.description}
218                 placeholder={i18n.t("description")}
219                 onContentChange={this.handleCommunityDescriptionChange}
220                 allLanguages={[]}
221                 siteLanguages={[]}
222               />
223             </div>
224           </div>
225
226           {this.props.enableNsfw && (
227             <div className="form-group row">
228               <legend className="col-form-label col-sm-2 pt-0">
229                 {i18n.t("nsfw")}
230               </legend>
231               <div className="col-10">
232                 <div className="form-check">
233                   <input
234                     className="form-check-input position-static"
235                     id="community-nsfw"
236                     type="checkbox"
237                     checked={this.state.form.nsfw}
238                     onChange={linkEvent(this, this.handleCommunityNsfwChange)}
239                   />
240                 </div>
241               </div>
242             </div>
243           )}
244           <div className="form-group row">
245             <legend className="col-form-label col-6 pt-0">
246               {i18n.t("only_mods_can_post_in_community")}
247             </legend>
248             <div className="col-6">
249               <div className="form-check">
250                 <input
251                   className="form-check-input position-static"
252                   id="community-only-mods-can-post"
253                   type="checkbox"
254                   checked={this.state.form.posting_restricted_to_mods}
255                   onChange={linkEvent(
256                     this,
257                     this.handleCommunityPostingRestrictedToMods
258                   )}
259                 />
260               </div>
261             </div>
262           </div>
263           <LanguageSelect
264             allLanguages={this.props.allLanguages}
265             siteLanguages={this.props.siteLanguages}
266             showSite
267             selectedLanguageIds={this.state.form.discussion_languages}
268             multiple={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 }