]> Untitled Git - lemmy.git/blob - ui/src/components/community-form.tsx
Merge remote-tracking branch 'upstream/master'
[lemmy.git] / ui / src / components / community-form.tsx
1 import { Component, linkEvent } from 'inferno';
2 import { Subscription } from 'rxjs';
3 import { retryWhen, delay, take } from 'rxjs/operators';
4 import {
5   CommunityForm as CommunityFormI,
6   UserOperation,
7   Category,
8   ListCategoriesResponse,
9   CommunityResponse,
10   GetSiteResponse,
11   WebSocketJsonResponse,
12 } from '../interfaces';
13 import { WebSocketService } from '../services';
14 import {
15   wsJsonToRes,
16   capitalizeFirstLetter,
17   toast,
18   randomStr,
19   setupTribute,
20 } from '../utils';
21 import Tribute from 'tributejs/src/Tribute.js';
22 import autosize from 'autosize';
23 import { i18n } from '../i18next';
24
25 import { Community } from '../interfaces';
26
27 interface CommunityFormProps {
28   community?: Community; // If a community is given, that means this is an edit
29   onCancel?(): any;
30   onCreate?(community: Community): any;
31   onEdit?(community: Community): any;
32 }
33
34 interface CommunityFormState {
35   communityForm: CommunityFormI;
36   categories: Array<Category>;
37   loading: boolean;
38   enable_nsfw: boolean;
39 }
40
41 export class CommunityForm extends Component<
42   CommunityFormProps,
43   CommunityFormState
44 > {
45   private id = `community-form-${randomStr()}`;
46   private tribute: Tribute;
47   private subscription: Subscription;
48
49   private emptyState: CommunityFormState = {
50     communityForm: {
51       name: null,
52       title: null,
53       category_id: null,
54       nsfw: false,
55     },
56     categories: [],
57     loading: false,
58     enable_nsfw: null,
59   };
60
61   constructor(props: any, context: any) {
62     super(props, context);
63
64     this.tribute = setupTribute();
65     this.state = this.emptyState;
66
67     if (this.props.community) {
68       this.state.communityForm = {
69         name: this.props.community.name,
70         title: this.props.community.title,
71         category_id: this.props.community.category_id,
72         description: this.props.community.description,
73         edit_id: this.props.community.id,
74         nsfw: this.props.community.nsfw,
75         auth: null,
76       };
77     }
78
79     this.subscription = WebSocketService.Instance.subject
80       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
81       .subscribe(
82         msg => this.parseMessage(msg),
83         err => console.error(err),
84         () => console.log('complete')
85       );
86
87     WebSocketService.Instance.listCategories();
88     WebSocketService.Instance.getSite();
89   }
90
91   componentDidMount() {
92     var textarea: any = document.getElementById(this.id);
93     autosize(textarea);
94     this.tribute.attach(textarea);
95     textarea.addEventListener('tribute-replaced', () => {
96       this.state.communityForm.description = textarea.value;
97       this.setState(this.state);
98       autosize.update(textarea);
99     });
100   }
101
102   componentWillUnmount() {
103     this.subscription.unsubscribe();
104   }
105
106   render() {
107     return (
108       <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
109         <div class="form-group row">
110           <label class="col-12 col-form-label" htmlFor="community-name">
111             {i18n.t('name')}
112           </label>
113           <div class="col-12">
114             <input
115               type="text"
116               id="community-name"
117               class="form-control"
118               value={this.state.communityForm.name}
119               onInput={linkEvent(this, this.handleCommunityNameChange)}
120               required
121               minLength={3}
122               maxLength={20}
123               pattern="[a-z0-9_]+"
124               title={i18n.t('community_reqs')}
125             />
126           </div>
127         </div>
128
129         <div class="form-group row">
130           <label class="col-12 col-form-label" htmlFor="community-title">
131             {i18n.t('title')}
132           </label>
133           <div class="col-12">
134             <input
135               type="text"
136               id="community-title"
137               value={this.state.communityForm.title}
138               onInput={linkEvent(this, this.handleCommunityTitleChange)}
139               class="form-control"
140               required
141               minLength={3}
142               maxLength={100}
143             />
144           </div>
145         </div>
146         <div class="form-group row">
147           <label class="col-12 col-form-label" htmlFor={this.id}>
148             {i18n.t('sidebar')}
149           </label>
150           <div class="col-12">
151             <textarea
152               id={this.id}
153               value={this.state.communityForm.description}
154               onInput={linkEvent(this, this.handleCommunityDescriptionChange)}
155               class="form-control"
156               rows={3}
157               maxLength={10000}
158             />
159           </div>
160         </div>
161         <div class="form-group row">
162           <label class="col-12 col-form-label" htmlFor="community-category">
163             {i18n.t('category')}
164           </label>
165           <div class="col-12">
166             <select
167               class="form-control"
168               id="community-category"
169               value={this.state.communityForm.category_id}
170               onInput={linkEvent(this, this.handleCommunityCategoryChange)}
171             >
172               {this.state.categories.map(category => (
173                 <option value={category.id}>{category.name}</option>
174               ))}
175             </select>
176           </div>
177         </div>
178
179         {this.state.enable_nsfw && (
180           <div class="form-group row">
181             <div class="col-12">
182               <div class="form-check">
183                 <input
184                   class="form-check-input"
185                   id="community-nsfw"
186                   type="checkbox"
187                   checked={this.state.communityForm.nsfw}
188                   onChange={linkEvent(this, this.handleCommunityNsfwChange)}
189                 />
190                 <label class="form-check-label" htmlFor="community-nsfw">
191                   {i18n.t('nsfw')}
192                 </label>
193               </div>
194             </div>
195           </div>
196         )}
197         <div class="form-group row">
198           <div class="col-12">
199             <button type="submit" class="btn btn-secondary mr-2">
200               {this.state.loading ? (
201                 <svg class="icon icon-spinner spin">
202                   <use xlinkHref="#icon-spinner"></use>
203                 </svg>
204               ) : this.props.community ? (
205                 capitalizeFirstLetter(i18n.t('save'))
206               ) : (
207                 capitalizeFirstLetter(i18n.t('create'))
208               )}
209             </button>
210             {this.props.community && (
211               <button
212                 type="button"
213                 class="btn btn-secondary"
214                 onClick={linkEvent(this, this.handleCancel)}
215               >
216                 {i18n.t('cancel')}
217               </button>
218             )}
219           </div>
220         </div>
221       </form>
222     );
223   }
224
225   handleCreateCommunitySubmit(i: CommunityForm, event: any) {
226     event.preventDefault();
227     i.state.loading = true;
228     if (i.props.community) {
229       WebSocketService.Instance.editCommunity(i.state.communityForm);
230     } else {
231       WebSocketService.Instance.createCommunity(i.state.communityForm);
232     }
233     i.setState(i.state);
234   }
235
236   handleCommunityNameChange(i: CommunityForm, event: any) {
237     i.state.communityForm.name = event.target.value;
238     i.setState(i.state);
239   }
240
241   handleCommunityTitleChange(i: CommunityForm, event: any) {
242     i.state.communityForm.title = event.target.value;
243     i.setState(i.state);
244   }
245
246   handleCommunityDescriptionChange(i: CommunityForm, event: any) {
247     i.state.communityForm.description = event.target.value;
248     i.setState(i.state);
249   }
250
251   handleCommunityCategoryChange(i: CommunityForm, event: any) {
252     i.state.communityForm.category_id = Number(event.target.value);
253     i.setState(i.state);
254   }
255
256   handleCommunityNsfwChange(i: CommunityForm, event: any) {
257     i.state.communityForm.nsfw = event.target.checked;
258     i.setState(i.state);
259   }
260
261   handleCancel(i: CommunityForm) {
262     i.props.onCancel();
263   }
264
265   parseMessage(msg: WebSocketJsonResponse) {
266     let res = wsJsonToRes(msg);
267     console.log(msg);
268     if (msg.error) {
269       toast(i18n.t(msg.error), 'danger');
270       this.state.loading = false;
271       this.setState(this.state);
272       return;
273     } else if (res.op == UserOperation.ListCategories) {
274       let data = res.data as ListCategoriesResponse;
275       this.state.categories = data.categories;
276       if (!this.props.community) {
277         this.state.communityForm.category_id = data.categories[0].id;
278       }
279       this.setState(this.state);
280     } else if (res.op == UserOperation.CreateCommunity) {
281       let data = res.data as CommunityResponse;
282       this.state.loading = false;
283       this.props.onCreate(data.community);
284     }
285     // TODO is this necessary
286     else if (res.op == UserOperation.EditCommunity) {
287       let data = res.data as CommunityResponse;
288       this.state.loading = false;
289       this.props.onEdit(data.community);
290     } else if (res.op == UserOperation.GetSite) {
291       let data = res.data as GetSiteResponse;
292       this.state.enable_nsfw = data.site.enable_nsfw;
293       this.setState(this.state);
294     }
295   }
296 }