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