1 import { Component, linkEvent } from 'inferno';
2 import { Helmet } from 'inferno-helmet';
3 import { Subscription } from 'rxjs';
4 import { retryWhen, delay, take } from 'rxjs/operators';
12 GetUserMentionsResponse,
15 WebSocketJsonResponse,
16 PrivateMessage as PrivateMessageI,
17 GetPrivateMessagesForm,
18 PrivateMessagesResponse,
19 PrivateMessageResponse,
22 } from 'lemmy-js-client';
23 import { WebSocketService, UserService } from '../services';
35 import { CommentNodes } from './comment-nodes';
36 import { PrivateMessage } from './private-message';
37 import { SortSelect } from './sort-select';
38 import { i18n } from '../i18next';
52 type ReplyType = Comment | PrivateMessageI;
54 interface InboxState {
55 unreadOrAll: UnreadOrAll;
56 messageType: MessageType;
59 messages: PrivateMessageI[];
65 export class Inbox extends Component<any, InboxState> {
66 private subscription: Subscription;
67 private emptyState: InboxState = {
68 unreadOrAll: UnreadOrAll.Unread,
69 messageType: MessageType.All,
78 creator_id: undefined,
80 creator_name: undefined,
81 number_of_users: undefined,
82 number_of_posts: undefined,
83 number_of_comments: undefined,
84 number_of_communities: undefined,
85 enable_downvotes: undefined,
86 open_registration: undefined,
87 enable_nsfw: undefined,
91 constructor(props: any, context: any) {
92 super(props, context);
94 this.state = this.emptyState;
95 this.handleSortChange = this.handleSortChange.bind(this);
97 this.subscription = WebSocketService.Instance.subject
98 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
100 msg => this.parseMessage(msg),
101 err => console.error(err),
102 () => console.log('complete')
106 WebSocketService.Instance.getSite();
109 componentWillUnmount() {
110 this.subscription.unsubscribe();
113 get documentTitle(): string {
114 if (this.state.site.name) {
115 return `@${UserService.Instance.user.name} ${i18n.t('inbox')} - ${
125 <div class="container">
126 <Helmet title={this.documentTitle} />
133 href={`/feeds/inbox/${UserService.Instance.auth}.xml`}
138 <svg class="icon ml-2 text-muted small">
139 <use xlinkHref="#icon-rss">#</use>
144 {this.state.replies.length +
145 this.state.mentions.length +
146 this.state.messages.length >
148 this.state.unreadOrAll == UnreadOrAll.Unread && (
149 <ul class="list-inline mb-1 text-muted small font-weight-bold">
150 <li className="list-inline-item">
153 onClick={linkEvent(this, this.markAllAsRead)}
155 {i18n.t('mark_all_as_read')}
161 {this.state.messageType == MessageType.All && this.all()}
162 {this.state.messageType == MessageType.Replies && this.replies()}
163 {this.state.messageType == MessageType.Mentions && this.mentions()}
164 {this.state.messageType == MessageType.Messages && this.messages()}
172 unreadOrAllRadios() {
174 <div class="btn-group btn-group-toggle flex-wrap mb-2">
176 className={`btn btn-outline-secondary pointer
177 ${this.state.unreadOrAll == UnreadOrAll.Unread && 'active'}
182 value={UnreadOrAll.Unread}
183 checked={this.state.unreadOrAll == UnreadOrAll.Unread}
184 onChange={linkEvent(this, this.handleUnreadOrAllChange)}
189 className={`btn btn-outline-secondary pointer
190 ${this.state.unreadOrAll == UnreadOrAll.All && 'active'}
195 value={UnreadOrAll.All}
196 checked={this.state.unreadOrAll == UnreadOrAll.All}
197 onChange={linkEvent(this, this.handleUnreadOrAllChange)}
205 messageTypeRadios() {
207 <div class="btn-group btn-group-toggle flex-wrap mb-2">
209 className={`btn btn-outline-secondary pointer
210 ${this.state.messageType == MessageType.All && 'active'}
215 value={MessageType.All}
216 checked={this.state.messageType == MessageType.All}
217 onChange={linkEvent(this, this.handleMessageTypeChange)}
222 className={`btn btn-outline-secondary pointer
223 ${this.state.messageType == MessageType.Replies && 'active'}
228 value={MessageType.Replies}
229 checked={this.state.messageType == MessageType.Replies}
230 onChange={linkEvent(this, this.handleMessageTypeChange)}
235 className={`btn btn-outline-secondary pointer
236 ${this.state.messageType == MessageType.Mentions && 'active'}
241 value={MessageType.Mentions}
242 checked={this.state.messageType == MessageType.Mentions}
243 onChange={linkEvent(this, this.handleMessageTypeChange)}
248 className={`btn btn-outline-secondary pointer
249 ${this.state.messageType == MessageType.Messages && 'active'}
254 value={MessageType.Messages}
255 checked={this.state.messageType == MessageType.Messages}
256 onChange={linkEvent(this, this.handleMessageTypeChange)}
266 <div className="mb-2">
267 <span class="mr-3">{this.unreadOrAllRadios()}</span>
268 <span class="mr-3">{this.messageTypeRadios()}</span>
270 sort={this.state.sort}
271 onChange={this.handleSortChange}
278 combined(): ReplyType[] {
280 ...this.state.replies,
281 ...this.state.mentions,
282 ...this.state.messages,
283 ].sort((a, b) => b.published.localeCompare(a.published));
289 {this.combined().map(i =>
293 nodes={[{ comment: i }]}
298 enableDownvotes={this.state.site.enable_downvotes}
301 <PrivateMessage key={i.id} privateMessage={i} />
312 nodes={commentsToFlatNodes(this.state.replies)}
317 enableDownvotes={this.state.site.enable_downvotes}
326 {this.state.mentions.map(mention => (
329 nodes={[{ comment: mention }]}
334 enableDownvotes={this.state.site.enable_downvotes}
344 {this.state.messages.map(message => (
345 <PrivateMessage key={message.id} privateMessage={message} />
354 {this.state.page > 1 && (
356 class="btn btn-secondary mr-1"
357 onClick={linkEvent(this, this.prevPage)}
362 {this.unreadCount() > 0 && (
364 class="btn btn-secondary"
365 onClick={linkEvent(this, this.nextPage)}
386 handleUnreadOrAllChange(i: Inbox, event: any) {
387 i.state.unreadOrAll = Number(event.target.value);
393 handleMessageTypeChange(i: Inbox, event: any) {
394 i.state.messageType = Number(event.target.value);
401 let repliesForm: GetRepliesForm = {
402 sort: this.state.sort,
403 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
404 page: this.state.page,
407 WebSocketService.Instance.getReplies(repliesForm);
409 let userMentionsForm: GetUserMentionsForm = {
410 sort: this.state.sort,
411 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
412 page: this.state.page,
415 WebSocketService.Instance.getUserMentions(userMentionsForm);
417 let privateMessagesForm: GetPrivateMessagesForm = {
418 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
419 page: this.state.page,
422 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
425 handleSortChange(val: SortType) {
426 this.state.sort = val;
428 this.setState(this.state);
432 markAllAsRead(i: Inbox) {
433 WebSocketService.Instance.markAllAsRead();
434 i.state.replies = [];
435 i.state.mentions = [];
436 i.state.messages = [];
438 window.scrollTo(0, 0);
442 parseMessage(msg: WebSocketJsonResponse) {
444 let res = wsJsonToRes(msg);
446 toast(i18n.t(msg.error), 'danger');
448 } else if (msg.reconnect) {
450 } else if (res.op == UserOperation.GetReplies) {
451 let data = res.data as GetRepliesResponse;
452 this.state.replies = data.replies;
453 this.sendUnreadCount();
454 window.scrollTo(0, 0);
455 this.setState(this.state);
457 } else if (res.op == UserOperation.GetUserMentions) {
458 let data = res.data as GetUserMentionsResponse;
459 this.state.mentions = data.mentions;
460 this.sendUnreadCount();
461 window.scrollTo(0, 0);
462 this.setState(this.state);
464 } else if (res.op == UserOperation.GetPrivateMessages) {
465 let data = res.data as PrivateMessagesResponse;
466 this.state.messages = data.messages;
467 this.sendUnreadCount();
468 window.scrollTo(0, 0);
469 this.setState(this.state);
471 } else if (res.op == UserOperation.EditPrivateMessage) {
472 let data = res.data as PrivateMessageResponse;
473 let found: PrivateMessageI = this.state.messages.find(
474 m => m.id === data.message.id
477 found.content = data.message.content;
478 found.updated = data.message.updated;
480 this.setState(this.state);
481 } else if (res.op == UserOperation.DeletePrivateMessage) {
482 let data = res.data as PrivateMessageResponse;
483 let found: PrivateMessageI = this.state.messages.find(
484 m => m.id === data.message.id
487 found.deleted = data.message.deleted;
488 found.updated = data.message.updated;
490 this.setState(this.state);
491 } else if (res.op == UserOperation.MarkPrivateMessageAsRead) {
492 let data = res.data as PrivateMessageResponse;
493 let found: PrivateMessageI = this.state.messages.find(
494 m => m.id === data.message.id
498 found.updated = data.message.updated;
500 // If youre in the unread view, just remove it from the list
501 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) {
502 this.state.messages = this.state.messages.filter(
503 r => r.id !== data.message.id
506 let found = this.state.messages.find(c => c.id == data.message.id);
507 found.read = data.message.read;
510 this.sendUnreadCount();
511 this.setState(this.state);
512 } else if (res.op == UserOperation.MarkAllAsRead) {
513 // Moved to be instant
515 res.op == UserOperation.EditComment ||
516 res.op == UserOperation.DeleteComment ||
517 res.op == UserOperation.RemoveComment
519 let data = res.data as CommentResponse;
520 editCommentRes(data, this.state.replies);
521 this.setState(this.state);
522 } else if (res.op == UserOperation.MarkCommentAsRead) {
523 let data = res.data as CommentResponse;
525 // If youre in the unread view, just remove it from the list
526 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.comment.read) {
527 this.state.replies = this.state.replies.filter(
528 r => r.id !== data.comment.id
531 let found = this.state.replies.find(c => c.id == data.comment.id);
532 found.read = data.comment.read;
534 this.sendUnreadCount();
535 this.setState(this.state);
537 } else if (res.op == UserOperation.MarkUserMentionAsRead) {
538 let data = res.data as UserMentionResponse;
540 let found = this.state.mentions.find(c => c.id == data.mention.id);
541 found.content = data.mention.content;
542 found.updated = data.mention.updated;
543 found.removed = data.mention.removed;
544 found.deleted = data.mention.deleted;
545 found.upvotes = data.mention.upvotes;
546 found.downvotes = data.mention.downvotes;
547 found.score = data.mention.score;
549 // If youre in the unread view, just remove it from the list
550 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.mention.read) {
551 this.state.mentions = this.state.mentions.filter(
552 r => r.id !== data.mention.id
555 let found = this.state.mentions.find(c => c.id == data.mention.id);
556 found.read = data.mention.read;
558 this.sendUnreadCount();
559 this.setState(this.state);
560 } else if (res.op == UserOperation.CreateComment) {
561 let data = res.data as CommentResponse;
563 if (data.recipient_ids.includes(UserService.Instance.user.id)) {
564 this.state.replies.unshift(data.comment);
565 this.setState(this.state);
566 } else if (data.comment.creator_id == UserService.Instance.user.id) {
567 toast(i18n.t('reply_sent'));
569 } else if (res.op == UserOperation.CreatePrivateMessage) {
570 let data = res.data as PrivateMessageResponse;
571 if (data.message.recipient_id == UserService.Instance.user.id) {
572 this.state.messages.unshift(data.message);
573 this.setState(this.state);
575 } else if (res.op == UserOperation.SaveComment) {
576 let data = res.data as CommentResponse;
577 saveCommentRes(data, this.state.replies);
578 this.setState(this.state);
580 } else if (res.op == UserOperation.CreateCommentLike) {
581 let data = res.data as CommentResponse;
582 createCommentLikeRes(data, this.state.replies);
583 this.setState(this.state);
584 } else if (res.op == UserOperation.GetSite) {
585 let data = res.data as GetSiteResponse;
586 this.state.site = data.site;
587 this.setState(this.state);
592 UserService.Instance.unreadCountSub.next(this.unreadCount());
595 unreadCount(): number {
597 this.state.replies.filter(r => !r.read).length +
598 this.state.mentions.filter(r => !r.read).length +
599 this.state.messages.filter(
601 UserService.Instance.user &&
603 r.creator_id !== UserService.Instance.user.id