]> Untitled Git - lemmy.git/blob - ui/src/components/community.tsx
Merge branch 'nutomic-auto-setup' into dev
[lemmy.git] / ui / src / components / community.tsx
1 import { Component, linkEvent } from 'inferno';
2 import { Subscription } from 'rxjs';
3 import { retryWhen, delay, take } from 'rxjs/operators';
4 import {
5   UserOperation,
6   Community as CommunityI,
7   GetCommunityResponse,
8   CommunityResponse,
9   CommunityUser,
10   UserView,
11   SortType,
12   Post,
13   GetPostsForm,
14   GetCommunityForm,
15   ListingType,
16   DataType,
17   GetPostsResponse,
18   PostResponse,
19   AddModToCommunityResponse,
20   BanFromCommunityResponse,
21   Comment,
22   GetCommentsForm,
23   GetCommentsResponse,
24   CommentResponse,
25   WebSocketJsonResponse,
26 } from '../interfaces';
27 import { WebSocketService } from '../services';
28 import { PostListings } from './post-listings';
29 import { CommentNodes } from './comment-nodes';
30 import { SortSelect } from './sort-select';
31 import { DataTypeSelect } from './data-type-select';
32 import { Sidebar } from './sidebar';
33 import {
34   wsJsonToRes,
35   fetchLimit,
36   toast,
37   getPageFromProps,
38   getSortTypeFromProps,
39   getDataTypeFromProps,
40   editCommentRes,
41   saveCommentRes,
42   createCommentLikeRes,
43   createPostLikeFindRes,
44   editPostFindRes,
45   commentsToFlatNodes,
46   setupTippy,
47 } from '../utils';
48 import { i18n } from '../i18next';
49
50 interface State {
51   community: CommunityI;
52   communityId: number;
53   communityName: string;
54   moderators: Array<CommunityUser>;
55   admins: Array<UserView>;
56   online: number;
57   loading: boolean;
58   posts: Array<Post>;
59   comments: Array<Comment>;
60   dataType: DataType;
61   sort: SortType;
62   page: number;
63 }
64
65 export class Community extends Component<any, State> {
66   private subscription: Subscription;
67   private emptyState: State = {
68     community: {
69       id: null,
70       name: null,
71       title: null,
72       category_id: null,
73       category_name: null,
74       creator_id: null,
75       creator_name: null,
76       number_of_subscribers: null,
77       number_of_posts: null,
78       number_of_comments: null,
79       published: null,
80       removed: null,
81       nsfw: false,
82       deleted: null,
83     },
84     moderators: [],
85     admins: [],
86     communityId: Number(this.props.match.params.id),
87     communityName: this.props.match.params.name,
88     online: null,
89     loading: true,
90     posts: [],
91     comments: [],
92     dataType: getDataTypeFromProps(this.props),
93     sort: getSortTypeFromProps(this.props),
94     page: getPageFromProps(this.props),
95   };
96
97   constructor(props: any, context: any) {
98     super(props, context);
99
100     this.state = this.emptyState;
101     this.handleSortChange = this.handleSortChange.bind(this);
102     this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
103
104     this.subscription = WebSocketService.Instance.subject
105       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
106       .subscribe(
107         msg => this.parseMessage(msg),
108         err => console.error(err),
109         () => console.log('complete')
110       );
111
112     let form: GetCommunityForm = {
113       id: this.state.communityId ? this.state.communityId : null,
114       name: this.state.communityName ? this.state.communityName : null,
115     };
116     WebSocketService.Instance.getCommunity(form);
117   }
118
119   componentWillUnmount() {
120     this.subscription.unsubscribe();
121   }
122
123   // Necessary for back button for some reason
124   componentWillReceiveProps(nextProps: any) {
125     if (
126       nextProps.history.action == 'POP' ||
127       nextProps.history.action == 'PUSH'
128     ) {
129       this.state.dataType = getDataTypeFromProps(nextProps);
130       this.state.sort = getSortTypeFromProps(nextProps);
131       this.state.page = getPageFromProps(nextProps);
132       this.setState(this.state);
133       this.fetchData();
134     }
135   }
136
137   render() {
138     return (
139       <div class="container">
140         {this.selects()}
141         {this.state.loading ? (
142           <h5>
143             <svg class="icon icon-spinner spin">
144               <use xlinkHref="#icon-spinner"></use>
145             </svg>
146           </h5>
147         ) : (
148           <div class="row">
149             <div class="col-12 col-md-8">
150               <h5>
151                 {this.state.community.title}
152                 {this.state.community.removed && (
153                   <small className="ml-2 text-muted font-italic">
154                     {i18n.t('removed')}
155                   </small>
156                 )}
157                 {this.state.community.nsfw && (
158                   <small className="ml-2 text-muted font-italic">
159                     {i18n.t('nsfw')}
160                   </small>
161                 )}
162               </h5>
163               {this.listings()}
164               {this.paginator()}
165             </div>
166             <div class="col-12 col-md-4">
167               <Sidebar
168                 community={this.state.community}
169                 moderators={this.state.moderators}
170                 admins={this.state.admins}
171                 online={this.state.online}
172               />
173             </div>
174           </div>
175         )}
176       </div>
177     );
178   }
179
180   listings() {
181     return this.state.dataType == DataType.Post ? (
182       <PostListings
183         posts={this.state.posts}
184         removeDuplicates
185         sort={this.state.sort}
186       />
187     ) : (
188       <CommentNodes
189         nodes={commentsToFlatNodes(this.state.comments)}
190         noIndent
191         sortType={this.state.sort}
192         showContext
193       />
194     );
195   }
196
197   selects() {
198     return (
199       <div class="mb-3">
200         <span class="mr-3">
201           <DataTypeSelect
202             type_={this.state.dataType}
203             onChange={this.handleDataTypeChange}
204           />
205         </span>
206         <span class="mr-2">
207           <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
208         </span>
209         <a
210           href={`/feeds/c/${this.state.communityName}.xml?sort=${
211             SortType[this.state.sort]
212           }`}
213           target="_blank"
214           title="RSS"
215         >
216           <svg class="icon text-muted small">
217             <use xlinkHref="#icon-rss">#</use>
218           </svg>
219         </a>
220       </div>
221     );
222   }
223
224   paginator() {
225     return (
226       <div class="my-2">
227         {this.state.page > 1 && (
228           <button
229             class="btn btn-sm btn-secondary mr-1"
230             onClick={linkEvent(this, this.prevPage)}
231           >
232             {i18n.t('prev')}
233           </button>
234         )}
235         {this.state.posts.length == fetchLimit && (
236           <button
237             class="btn btn-sm btn-secondary"
238             onClick={linkEvent(this, this.nextPage)}
239           >
240             {i18n.t('next')}
241           </button>
242         )}
243       </div>
244     );
245   }
246
247   nextPage(i: Community) {
248     i.state.page++;
249     i.setState(i.state);
250     i.updateUrl();
251     i.fetchData();
252     window.scrollTo(0, 0);
253   }
254
255   prevPage(i: Community) {
256     i.state.page--;
257     i.setState(i.state);
258     i.updateUrl();
259     i.fetchData();
260     window.scrollTo(0, 0);
261   }
262
263   handleSortChange(val: SortType) {
264     this.state.sort = val;
265     this.state.page = 1;
266     this.state.loading = true;
267     this.setState(this.state);
268     this.updateUrl();
269     this.fetchData();
270     window.scrollTo(0, 0);
271   }
272
273   handleDataTypeChange(val: DataType) {
274     this.state.dataType = val;
275     this.state.page = 1;
276     this.state.loading = true;
277     this.setState(this.state);
278     this.updateUrl();
279     this.fetchData();
280     window.scrollTo(0, 0);
281   }
282
283   updateUrl() {
284     let dataTypeStr = DataType[this.state.dataType].toLowerCase();
285     let sortStr = SortType[this.state.sort].toLowerCase();
286     this.props.history.push(
287       `/c/${this.state.community.name}/data_type/${dataTypeStr}/sort/${sortStr}/page/${this.state.page}`
288     );
289   }
290
291   fetchData() {
292     if (this.state.dataType == DataType.Post) {
293       let getPostsForm: GetPostsForm = {
294         page: this.state.page,
295         limit: fetchLimit,
296         sort: SortType[this.state.sort],
297         type_: ListingType[ListingType.Community],
298         community_id: this.state.community.id,
299       };
300       WebSocketService.Instance.getPosts(getPostsForm);
301     } else {
302       let getCommentsForm: GetCommentsForm = {
303         page: this.state.page,
304         limit: fetchLimit,
305         sort: SortType[this.state.sort],
306         type_: ListingType[ListingType.Community],
307         community_id: this.state.community.id,
308       };
309       WebSocketService.Instance.getComments(getCommentsForm);
310     }
311   }
312
313   parseMessage(msg: WebSocketJsonResponse) {
314     console.log(msg);
315     let res = wsJsonToRes(msg);
316     if (msg.error) {
317       toast(i18n.t(msg.error), 'danger');
318       this.context.router.history.push('/');
319       return;
320     } else if (msg.reconnect) {
321       this.fetchData();
322     } else if (res.op == UserOperation.GetCommunity) {
323       let data = res.data as GetCommunityResponse;
324       this.state.community = data.community;
325       this.state.moderators = data.moderators;
326       this.state.admins = data.admins;
327       this.state.online = data.online;
328       document.title = `/c/${this.state.community.name} - ${WebSocketService.Instance.site.name}`;
329       this.setState(this.state);
330       this.fetchData();
331     } else if (res.op == UserOperation.EditCommunity) {
332       let data = res.data as CommunityResponse;
333       this.state.community = data.community;
334       this.setState(this.state);
335     } else if (res.op == UserOperation.FollowCommunity) {
336       let data = res.data as CommunityResponse;
337       this.state.community.subscribed = data.community.subscribed;
338       this.state.community.number_of_subscribers =
339         data.community.number_of_subscribers;
340       this.setState(this.state);
341     } else if (res.op == UserOperation.GetPosts) {
342       let data = res.data as GetPostsResponse;
343       this.state.posts = data.posts;
344       this.state.loading = false;
345       this.setState(this.state);
346       setupTippy();
347     } else if (res.op == UserOperation.EditPost) {
348       let data = res.data as PostResponse;
349       editPostFindRes(data, this.state.posts);
350       this.setState(this.state);
351     } else if (res.op == UserOperation.CreatePost) {
352       let data = res.data as PostResponse;
353       this.state.posts.unshift(data.post);
354       this.setState(this.state);
355     } else if (res.op == UserOperation.CreatePostLike) {
356       let data = res.data as PostResponse;
357       createPostLikeFindRes(data, this.state.posts);
358       this.setState(this.state);
359     } else if (res.op == UserOperation.AddModToCommunity) {
360       let data = res.data as AddModToCommunityResponse;
361       this.state.moderators = data.moderators;
362       this.setState(this.state);
363     } else if (res.op == UserOperation.BanFromCommunity) {
364       let data = res.data as BanFromCommunityResponse;
365
366       this.state.posts
367         .filter(p => p.creator_id == data.user.id)
368         .forEach(p => (p.banned = data.banned));
369
370       this.setState(this.state);
371     } else if (res.op == UserOperation.GetComments) {
372       let data = res.data as GetCommentsResponse;
373       this.state.comments = data.comments;
374       this.state.loading = false;
375       this.setState(this.state);
376     } else if (res.op == UserOperation.EditComment) {
377       let data = res.data as CommentResponse;
378       editCommentRes(data, this.state.comments);
379       this.setState(this.state);
380     } else if (res.op == UserOperation.CreateComment) {
381       let data = res.data as CommentResponse;
382
383       // Necessary since it might be a user reply
384       if (data.recipient_ids.length == 0) {
385         this.state.comments.unshift(data.comment);
386         this.setState(this.state);
387       }
388     } else if (res.op == UserOperation.SaveComment) {
389       let data = res.data as CommentResponse;
390       saveCommentRes(data, this.state.comments);
391       this.setState(this.state);
392     } else if (res.op == UserOperation.CreateCommentLike) {
393       let data = res.data as CommentResponse;
394       createCommentLikeRes(data, this.state.comments);
395       this.setState(this.state);
396     }
397   }
398 }