]> Untitled Git - lemmy.git/blob - ui/src/components/inbox.tsx
Removing some commented lines from the dockerfile.
[lemmy.git] / ui / src / components / inbox.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   Comment,
7   SortType,
8   GetRepliesForm,
9   GetRepliesResponse,
10   GetUserMentionsForm,
11   GetUserMentionsResponse,
12   UserMentionResponse,
13   CommentResponse,
14   WebSocketJsonResponse,
15   PrivateMessage as PrivateMessageI,
16   GetPrivateMessagesForm,
17   PrivateMessagesResponse,
18   PrivateMessageResponse,
19 } from '../interfaces';
20 import { WebSocketService, UserService } from '../services';
21 import {
22   wsJsonToRes,
23   fetchLimit,
24   isCommentType,
25   toast,
26   editCommentRes,
27   saveCommentRes,
28   createCommentLikeRes,
29   commentsToFlatNodes,
30   setupTippy,
31 } from '../utils';
32 import { CommentNodes } from './comment-nodes';
33 import { PrivateMessage } from './private-message';
34 import { SortSelect } from './sort-select';
35 import { i18n } from '../i18next';
36
37 enum UnreadOrAll {
38   Unread,
39   All,
40 }
41
42 enum MessageType {
43   All,
44   Replies,
45   Mentions,
46   Messages,
47 }
48
49 type ReplyType = Comment | PrivateMessageI;
50
51 interface InboxState {
52   unreadOrAll: UnreadOrAll;
53   messageType: MessageType;
54   replies: Array<Comment>;
55   mentions: Array<Comment>;
56   messages: Array<PrivateMessageI>;
57   sort: SortType;
58   page: number;
59 }
60
61 export class Inbox extends Component<any, InboxState> {
62   private subscription: Subscription;
63   private emptyState: InboxState = {
64     unreadOrAll: UnreadOrAll.Unread,
65     messageType: MessageType.All,
66     replies: [],
67     mentions: [],
68     messages: [],
69     sort: SortType.New,
70     page: 1,
71   };
72
73   constructor(props: any, context: any) {
74     super(props, context);
75
76     this.state = this.emptyState;
77     this.handleSortChange = this.handleSortChange.bind(this);
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     this.refetch();
88   }
89
90   componentWillUnmount() {
91     this.subscription.unsubscribe();
92   }
93
94   componentDidMount() {
95     document.title = `/u/${UserService.Instance.user.username} ${i18n.t(
96       'inbox'
97     )} - ${WebSocketService.Instance.site.name}`;
98   }
99
100   render() {
101     return (
102       <div class="container">
103         <div class="row">
104           <div class="col-12">
105             <h5 class="mb-1">
106               {i18n.t('inbox')}
107               <small>
108                 <a
109                   href={`/feeds/inbox/${UserService.Instance.auth}.xml`}
110                   target="_blank"
111                   title="RSS"
112                 >
113                   <svg class="icon ml-2 text-muted small">
114                     <use xlinkHref="#icon-rss">#</use>
115                   </svg>
116                 </a>
117               </small>
118             </h5>
119             {this.state.replies.length +
120               this.state.mentions.length +
121               this.state.messages.length >
122               0 &&
123               this.state.unreadOrAll == UnreadOrAll.Unread && (
124                 <ul class="list-inline mb-1 text-muted small font-weight-bold">
125                   <li className="list-inline-item">
126                     <span class="pointer" onClick={this.markAllAsRead}>
127                       {i18n.t('mark_all_as_read')}
128                     </span>
129                   </li>
130                 </ul>
131               )}
132             {this.selects()}
133             {this.state.messageType == MessageType.All && this.all()}
134             {this.state.messageType == MessageType.Replies && this.replies()}
135             {this.state.messageType == MessageType.Mentions && this.mentions()}
136             {this.state.messageType == MessageType.Messages && this.messages()}
137             {this.paginator()}
138           </div>
139         </div>
140       </div>
141     );
142   }
143
144   unreadOrAllRadios() {
145     return (
146       <div class="btn-group btn-group-toggle">
147         <label
148           className={`btn btn-sm btn-secondary pointer
149             ${this.state.unreadOrAll == UnreadOrAll.Unread && 'active'}
150           `}
151         >
152           <input
153             type="radio"
154             value={UnreadOrAll.Unread}
155             checked={this.state.unreadOrAll == UnreadOrAll.Unread}
156             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
157           />
158           {i18n.t('unread')}
159         </label>
160         <label
161           className={`btn btn-sm btn-secondary pointer
162             ${this.state.unreadOrAll == UnreadOrAll.All && 'active'}
163           `}
164         >
165           <input
166             type="radio"
167             value={UnreadOrAll.All}
168             checked={this.state.unreadOrAll == UnreadOrAll.All}
169             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
170           />
171           {i18n.t('all')}
172         </label>
173       </div>
174     );
175   }
176
177   messageTypeRadios() {
178     return (
179       <div class="btn-group btn-group-toggle">
180         <label
181           className={`btn btn-sm btn-secondary pointer btn-outline-light
182             ${this.state.messageType == MessageType.All && 'active'}
183           `}
184         >
185           <input
186             type="radio"
187             value={MessageType.All}
188             checked={this.state.messageType == MessageType.All}
189             onChange={linkEvent(this, this.handleMessageTypeChange)}
190           />
191           {i18n.t('all')}
192         </label>
193         <label
194           className={`btn btn-sm btn-secondary pointer btn-outline-light
195             ${this.state.messageType == MessageType.Replies && 'active'}
196           `}
197         >
198           <input
199             type="radio"
200             value={MessageType.Replies}
201             checked={this.state.messageType == MessageType.Replies}
202             onChange={linkEvent(this, this.handleMessageTypeChange)}
203           />
204           {i18n.t('replies')}
205         </label>
206         <label
207           className={`btn btn-sm btn-secondary pointer btn-outline-light
208             ${this.state.messageType == MessageType.Mentions && 'active'}
209           `}
210         >
211           <input
212             type="radio"
213             value={MessageType.Mentions}
214             checked={this.state.messageType == MessageType.Mentions}
215             onChange={linkEvent(this, this.handleMessageTypeChange)}
216           />
217           {i18n.t('mentions')}
218         </label>
219         <label
220           className={`btn btn-sm btn-secondary pointer btn-outline-light
221             ${this.state.messageType == MessageType.Messages && 'active'}
222           `}
223         >
224           <input
225             type="radio"
226             value={MessageType.Messages}
227             checked={this.state.messageType == MessageType.Messages}
228             onChange={linkEvent(this, this.handleMessageTypeChange)}
229           />
230           {i18n.t('messages')}
231         </label>
232       </div>
233     );
234   }
235
236   selects() {
237     return (
238       <div className="mb-2">
239         <span class="mr-3">{this.unreadOrAllRadios()}</span>
240         <span class="mr-3">{this.messageTypeRadios()}</span>
241         <SortSelect
242           sort={this.state.sort}
243           onChange={this.handleSortChange}
244           hideHot
245         />
246       </div>
247     );
248   }
249
250   all() {
251     let combined: Array<ReplyType> = [];
252
253     combined.push(...this.state.replies);
254     combined.push(...this.state.mentions);
255     combined.push(...this.state.messages);
256
257     // Sort it
258     combined.sort((a, b) => b.published.localeCompare(a.published));
259
260     return (
261       <div>
262         {combined.map(i =>
263           isCommentType(i) ? (
264             <CommentNodes
265               nodes={[{ comment: i }]}
266               noIndent
267               markable
268               showContext
269             />
270           ) : (
271             <PrivateMessage privateMessage={i} />
272           )
273         )}
274       </div>
275     );
276   }
277
278   replies() {
279     return (
280       <div>
281         <CommentNodes
282           nodes={commentsToFlatNodes(this.state.replies)}
283           noIndent
284           markable
285           showContext
286         />
287       </div>
288     );
289   }
290
291   mentions() {
292     return (
293       <div>
294         {this.state.mentions.map(mention => (
295           <CommentNodes
296             nodes={[{ comment: mention }]}
297             noIndent
298             markable
299             showContext
300           />
301         ))}
302       </div>
303     );
304   }
305
306   messages() {
307     return (
308       <div>
309         {this.state.messages.map(message => (
310           <PrivateMessage privateMessage={message} />
311         ))}
312       </div>
313     );
314   }
315
316   paginator() {
317     return (
318       <div class="mt-2">
319         {this.state.page > 1 && (
320           <button
321             class="btn btn-sm btn-secondary mr-1"
322             onClick={linkEvent(this, this.prevPage)}
323           >
324             {i18n.t('prev')}
325           </button>
326         )}
327         <button
328           class="btn btn-sm btn-secondary"
329           onClick={linkEvent(this, this.nextPage)}
330         >
331           {i18n.t('next')}
332         </button>
333       </div>
334     );
335   }
336
337   nextPage(i: Inbox) {
338     i.state.page++;
339     i.setState(i.state);
340     i.refetch();
341   }
342
343   prevPage(i: Inbox) {
344     i.state.page--;
345     i.setState(i.state);
346     i.refetch();
347   }
348
349   handleUnreadOrAllChange(i: Inbox, event: any) {
350     i.state.unreadOrAll = Number(event.target.value);
351     i.state.page = 1;
352     i.setState(i.state);
353     i.refetch();
354   }
355
356   handleMessageTypeChange(i: Inbox, event: any) {
357     i.state.messageType = Number(event.target.value);
358     i.state.page = 1;
359     i.setState(i.state);
360     i.refetch();
361   }
362
363   refetch() {
364     let repliesForm: GetRepliesForm = {
365       sort: SortType[this.state.sort],
366       unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
367       page: this.state.page,
368       limit: fetchLimit,
369     };
370     WebSocketService.Instance.getReplies(repliesForm);
371
372     let userMentionsForm: GetUserMentionsForm = {
373       sort: SortType[this.state.sort],
374       unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
375       page: this.state.page,
376       limit: fetchLimit,
377     };
378     WebSocketService.Instance.getUserMentions(userMentionsForm);
379
380     let privateMessagesForm: GetPrivateMessagesForm = {
381       unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
382       page: this.state.page,
383       limit: fetchLimit,
384     };
385     WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
386   }
387
388   handleSortChange(val: SortType) {
389     this.state.sort = val;
390     this.state.page = 1;
391     this.setState(this.state);
392     this.refetch();
393   }
394
395   markAllAsRead() {
396     WebSocketService.Instance.markAllAsRead();
397   }
398
399   parseMessage(msg: WebSocketJsonResponse) {
400     console.log(msg);
401     let res = wsJsonToRes(msg);
402     if (msg.error) {
403       toast(i18n.t(msg.error), 'danger');
404       return;
405     } else if (msg.reconnect) {
406       this.refetch();
407     } else if (res.op == UserOperation.GetReplies) {
408       let data = res.data as GetRepliesResponse;
409       this.state.replies = data.replies;
410       this.sendUnreadCount();
411       window.scrollTo(0, 0);
412       this.setState(this.state);
413       setupTippy();
414     } else if (res.op == UserOperation.GetUserMentions) {
415       let data = res.data as GetUserMentionsResponse;
416       this.state.mentions = data.mentions;
417       this.sendUnreadCount();
418       window.scrollTo(0, 0);
419       this.setState(this.state);
420       setupTippy();
421     } else if (res.op == UserOperation.GetPrivateMessages) {
422       let data = res.data as PrivateMessagesResponse;
423       this.state.messages = data.messages;
424       this.sendUnreadCount();
425       window.scrollTo(0, 0);
426       this.setState(this.state);
427       setupTippy();
428     } else if (res.op == UserOperation.EditPrivateMessage) {
429       let data = res.data as PrivateMessageResponse;
430       let found: PrivateMessageI = this.state.messages.find(
431         m => m.id === data.message.id
432       );
433       found.content = data.message.content;
434       found.updated = data.message.updated;
435       found.deleted = data.message.deleted;
436       // If youre in the unread view, just remove it from the list
437       if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) {
438         this.state.messages = this.state.messages.filter(
439           r => r.id !== data.message.id
440         );
441       } else {
442         let found = this.state.messages.find(c => c.id == data.message.id);
443         found.read = data.message.read;
444       }
445       this.sendUnreadCount();
446       window.scrollTo(0, 0);
447       this.setState(this.state);
448       setupTippy();
449     } else if (res.op == UserOperation.MarkAllAsRead) {
450       this.state.replies = [];
451       this.state.mentions = [];
452       this.state.messages = [];
453       this.sendUnreadCount();
454       window.scrollTo(0, 0);
455       this.setState(this.state);
456     } else if (res.op == UserOperation.EditComment) {
457       let data = res.data as CommentResponse;
458       editCommentRes(data, this.state.replies);
459
460       // If youre in the unread view, just remove it from the list
461       if (this.state.unreadOrAll == UnreadOrAll.Unread && data.comment.read) {
462         this.state.replies = this.state.replies.filter(
463           r => r.id !== data.comment.id
464         );
465       } else {
466         let found = this.state.replies.find(c => c.id == data.comment.id);
467         found.read = data.comment.read;
468       }
469       this.sendUnreadCount();
470       this.setState(this.state);
471       setupTippy();
472     } else if (res.op == UserOperation.EditUserMention) {
473       let data = res.data as UserMentionResponse;
474
475       let found = this.state.mentions.find(c => c.id == data.mention.id);
476       found.content = data.mention.content;
477       found.updated = data.mention.updated;
478       found.removed = data.mention.removed;
479       found.deleted = data.mention.deleted;
480       found.upvotes = data.mention.upvotes;
481       found.downvotes = data.mention.downvotes;
482       found.score = data.mention.score;
483
484       // If youre in the unread view, just remove it from the list
485       if (this.state.unreadOrAll == UnreadOrAll.Unread && data.mention.read) {
486         this.state.mentions = this.state.mentions.filter(
487           r => r.id !== data.mention.id
488         );
489       } else {
490         let found = this.state.mentions.find(c => c.id == data.mention.id);
491         found.read = data.mention.read;
492       }
493       this.sendUnreadCount();
494       this.setState(this.state);
495     } else if (res.op == UserOperation.CreateComment) {
496       let data = res.data as CommentResponse;
497
498       if (data.recipient_ids.includes(UserService.Instance.user.id)) {
499         this.state.replies.unshift(data.comment);
500         this.setState(this.state);
501       } else if (data.comment.creator_id == UserService.Instance.user.id) {
502         toast(i18n.t('reply_sent'));
503       }
504       this.setState(this.state);
505     } else if (res.op == UserOperation.CreatePrivateMessage) {
506       let data = res.data as PrivateMessageResponse;
507       if (data.message.recipient_id == UserService.Instance.user.id) {
508         this.state.messages.unshift(data.message);
509         this.setState(this.state);
510       }
511     } else if (res.op == UserOperation.SaveComment) {
512       let data = res.data as CommentResponse;
513       saveCommentRes(data, this.state.replies);
514       this.setState(this.state);
515       setupTippy();
516     } else if (res.op == UserOperation.CreateCommentLike) {
517       let data = res.data as CommentResponse;
518       createCommentLikeRes(data, this.state.replies);
519       this.setState(this.state);
520     }
521   }
522
523   sendUnreadCount() {
524     let count =
525       this.state.replies.filter(r => !r.read).length +
526       this.state.mentions.filter(r => !r.read).length +
527       this.state.messages.filter(
528         r => !r.read && r.creator_id !== UserService.Instance.user.id
529       ).length;
530     UserService.Instance.user.unreadCount = count;
531     UserService.Instance.sub.next({
532       user: UserService.Instance.user,
533     });
534   }
535 }