1 import { Component, linkEvent } from 'inferno';
2 import { Subscription } from 'rxjs';
3 import { retryWhen, delay, take } from 'rxjs/operators';
11 GetUserMentionsResponse,
14 WebSocketJsonResponse,
15 PrivateMessage as PrivateMessageI,
16 GetPrivateMessagesForm,
17 PrivateMessagesResponse,
18 PrivateMessageResponse,
19 } from '../interfaces';
20 import { WebSocketService, UserService } from '../services';
32 import { CommentNodes } from './comment-nodes';
33 import { PrivateMessage } from './private-message';
34 import { SortSelect } from './sort-select';
35 import { i18n } from '../i18next';
49 type ReplyType = Comment | PrivateMessageI;
51 interface InboxState {
52 unreadOrAll: UnreadOrAll;
53 messageType: MessageType;
54 replies: Array<Comment>;
55 mentions: Array<Comment>;
56 messages: Array<PrivateMessageI>;
61 export class Inbox extends Component<any, InboxState> {
62 private subscription: Subscription;
63 private emptyState: InboxState = {
64 unreadOrAll: UnreadOrAll.Unread,
65 messageType: MessageType.All,
73 constructor(props: any, context: any) {
74 super(props, context);
76 this.state = this.emptyState;
77 this.handleSortChange = this.handleSortChange.bind(this);
79 this.subscription = WebSocketService.Instance.subject
80 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
82 msg => this.parseMessage(msg),
83 err => console.error(err),
84 () => console.log('complete')
90 componentWillUnmount() {
91 this.subscription.unsubscribe();
95 document.title = `/u/${UserService.Instance.user.username} ${i18n.t(
97 )} - ${WebSocketService.Instance.site.name}`;
102 <div class="container">
109 href={`/feeds/inbox/${UserService.Instance.auth}.xml`}
113 <svg class="icon ml-2 text-muted small">
114 <use xlinkHref="#icon-rss">#</use>
119 {this.state.replies.length +
120 this.state.mentions.length +
121 this.state.messages.length >
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')}
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()}
144 unreadOrAllRadios() {
146 <div class="btn-group btn-group-toggle">
148 className={`btn btn-sm btn-secondary pointer
149 ${this.state.unreadOrAll == UnreadOrAll.Unread && 'active'}
154 value={UnreadOrAll.Unread}
155 checked={this.state.unreadOrAll == UnreadOrAll.Unread}
156 onChange={linkEvent(this, this.handleUnreadOrAllChange)}
161 className={`btn btn-sm btn-secondary pointer
162 ${this.state.unreadOrAll == UnreadOrAll.All && 'active'}
167 value={UnreadOrAll.All}
168 checked={this.state.unreadOrAll == UnreadOrAll.All}
169 onChange={linkEvent(this, this.handleUnreadOrAllChange)}
177 messageTypeRadios() {
179 <div class="btn-group btn-group-toggle">
181 className={`btn btn-sm btn-secondary pointer btn-outline-light
182 ${this.state.messageType == MessageType.All && 'active'}
187 value={MessageType.All}
188 checked={this.state.messageType == MessageType.All}
189 onChange={linkEvent(this, this.handleMessageTypeChange)}
194 className={`btn btn-sm btn-secondary pointer btn-outline-light
195 ${this.state.messageType == MessageType.Replies && 'active'}
200 value={MessageType.Replies}
201 checked={this.state.messageType == MessageType.Replies}
202 onChange={linkEvent(this, this.handleMessageTypeChange)}
207 className={`btn btn-sm btn-secondary pointer btn-outline-light
208 ${this.state.messageType == MessageType.Mentions && 'active'}
213 value={MessageType.Mentions}
214 checked={this.state.messageType == MessageType.Mentions}
215 onChange={linkEvent(this, this.handleMessageTypeChange)}
220 className={`btn btn-sm btn-secondary pointer btn-outline-light
221 ${this.state.messageType == MessageType.Messages && 'active'}
226 value={MessageType.Messages}
227 checked={this.state.messageType == MessageType.Messages}
228 onChange={linkEvent(this, this.handleMessageTypeChange)}
238 <div className="mb-2">
239 <span class="mr-3">{this.unreadOrAllRadios()}</span>
240 <span class="mr-3">{this.messageTypeRadios()}</span>
242 sort={this.state.sort}
243 onChange={this.handleSortChange}
251 let combined: Array<ReplyType> = [];
253 combined.push(...this.state.replies);
254 combined.push(...this.state.mentions);
255 combined.push(...this.state.messages);
258 combined.sort((a, b) => b.published.localeCompare(a.published));
265 nodes={[{ comment: i }]}
271 <PrivateMessage privateMessage={i} />
282 nodes={commentsToFlatNodes(this.state.replies)}
294 {this.state.mentions.map(mention => (
296 nodes={[{ comment: mention }]}
309 {this.state.messages.map(message => (
310 <PrivateMessage privateMessage={message} />
319 {this.state.page > 1 && (
321 class="btn btn-sm btn-secondary mr-1"
322 onClick={linkEvent(this, this.prevPage)}
328 class="btn btn-sm btn-secondary"
329 onClick={linkEvent(this, this.nextPage)}
349 handleUnreadOrAllChange(i: Inbox, event: any) {
350 i.state.unreadOrAll = Number(event.target.value);
356 handleMessageTypeChange(i: Inbox, event: any) {
357 i.state.messageType = Number(event.target.value);
364 let repliesForm: GetRepliesForm = {
365 sort: SortType[this.state.sort],
366 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
367 page: this.state.page,
370 WebSocketService.Instance.getReplies(repliesForm);
372 let userMentionsForm: GetUserMentionsForm = {
373 sort: SortType[this.state.sort],
374 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
375 page: this.state.page,
378 WebSocketService.Instance.getUserMentions(userMentionsForm);
380 let privateMessagesForm: GetPrivateMessagesForm = {
381 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
382 page: this.state.page,
385 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
388 handleSortChange(val: SortType) {
389 this.state.sort = val;
391 this.setState(this.state);
396 WebSocketService.Instance.markAllAsRead();
399 parseMessage(msg: WebSocketJsonResponse) {
401 let res = wsJsonToRes(msg);
403 toast(i18n.t(msg.error), 'danger');
405 } else if (msg.reconnect) {
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);
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);
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);
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
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
442 let found = this.state.messages.find(c => c.id == data.message.id);
443 found.read = data.message.read;
445 this.sendUnreadCount();
446 window.scrollTo(0, 0);
447 this.setState(this.state);
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);
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
466 let found = this.state.replies.find(c => c.id == data.comment.id);
467 found.read = data.comment.read;
469 this.sendUnreadCount();
470 this.setState(this.state);
472 } else if (res.op == UserOperation.EditUserMention) {
473 let data = res.data as UserMentionResponse;
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;
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
490 let found = this.state.mentions.find(c => c.id == data.mention.id);
491 found.read = data.mention.read;
493 this.sendUnreadCount();
494 this.setState(this.state);
495 } else if (res.op == UserOperation.CreateComment) {
496 let data = res.data as CommentResponse;
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'));
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);
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);
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);
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
530 UserService.Instance.user.unreadCount = count;
531 UserService.Instance.sub.next({
532 user: UserService.Instance.user,