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