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">
128 onClick={linkEvent(this, this.markAllAsRead)}
130 {i18n.t('mark_all_as_read')}
136 {this.state.messageType == MessageType.All && this.all()}
137 {this.state.messageType == MessageType.Replies && this.replies()}
138 {this.state.messageType == MessageType.Mentions && this.mentions()}
139 {this.state.messageType == MessageType.Messages && this.messages()}
147 unreadOrAllRadios() {
149 <div class="btn-group btn-group-toggle">
151 className={`btn btn-sm btn-secondary pointer
152 ${this.state.unreadOrAll == UnreadOrAll.Unread && 'active'}
157 value={UnreadOrAll.Unread}
158 checked={this.state.unreadOrAll == UnreadOrAll.Unread}
159 onChange={linkEvent(this, this.handleUnreadOrAllChange)}
164 className={`btn btn-sm btn-secondary pointer
165 ${this.state.unreadOrAll == UnreadOrAll.All && 'active'}
170 value={UnreadOrAll.All}
171 checked={this.state.unreadOrAll == UnreadOrAll.All}
172 onChange={linkEvent(this, this.handleUnreadOrAllChange)}
180 messageTypeRadios() {
182 <div class="btn-group btn-group-toggle">
184 className={`btn btn-sm btn-secondary pointer btn-outline-light
185 ${this.state.messageType == MessageType.All && 'active'}
190 value={MessageType.All}
191 checked={this.state.messageType == MessageType.All}
192 onChange={linkEvent(this, this.handleMessageTypeChange)}
197 className={`btn btn-sm btn-secondary pointer btn-outline-light
198 ${this.state.messageType == MessageType.Replies && 'active'}
203 value={MessageType.Replies}
204 checked={this.state.messageType == MessageType.Replies}
205 onChange={linkEvent(this, this.handleMessageTypeChange)}
210 className={`btn btn-sm btn-secondary pointer btn-outline-light
211 ${this.state.messageType == MessageType.Mentions && 'active'}
216 value={MessageType.Mentions}
217 checked={this.state.messageType == MessageType.Mentions}
218 onChange={linkEvent(this, this.handleMessageTypeChange)}
223 className={`btn btn-sm btn-secondary pointer btn-outline-light
224 ${this.state.messageType == MessageType.Messages && 'active'}
229 value={MessageType.Messages}
230 checked={this.state.messageType == MessageType.Messages}
231 onChange={linkEvent(this, this.handleMessageTypeChange)}
241 <div className="mb-2">
242 <span class="mr-3">{this.unreadOrAllRadios()}</span>
243 <span class="mr-3">{this.messageTypeRadios()}</span>
245 sort={this.state.sort}
246 onChange={this.handleSortChange}
254 let combined: Array<ReplyType> = [];
256 combined.push(...this.state.replies);
257 combined.push(...this.state.mentions);
258 combined.push(...this.state.messages);
261 combined.sort((a, b) => b.published.localeCompare(a.published));
268 nodes={[{ comment: i }]}
274 <PrivateMessage privateMessage={i} />
285 nodes={commentsToFlatNodes(this.state.replies)}
297 {this.state.mentions.map(mention => (
299 nodes={[{ comment: mention }]}
312 {this.state.messages.map(message => (
313 <PrivateMessage privateMessage={message} />
322 {this.state.page > 1 && (
324 class="btn btn-sm btn-secondary mr-1"
325 onClick={linkEvent(this, this.prevPage)}
331 class="btn btn-sm btn-secondary"
332 onClick={linkEvent(this, this.nextPage)}
352 handleUnreadOrAllChange(i: Inbox, event: any) {
353 i.state.unreadOrAll = Number(event.target.value);
359 handleMessageTypeChange(i: Inbox, event: any) {
360 i.state.messageType = Number(event.target.value);
367 let repliesForm: GetRepliesForm = {
368 sort: SortType[this.state.sort],
369 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
370 page: this.state.page,
373 WebSocketService.Instance.getReplies(repliesForm);
375 let userMentionsForm: GetUserMentionsForm = {
376 sort: SortType[this.state.sort],
377 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
378 page: this.state.page,
381 WebSocketService.Instance.getUserMentions(userMentionsForm);
383 let privateMessagesForm: GetPrivateMessagesForm = {
384 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
385 page: this.state.page,
388 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
391 handleSortChange(val: SortType) {
392 this.state.sort = val;
394 this.setState(this.state);
398 markAllAsRead(i: Inbox) {
399 WebSocketService.Instance.markAllAsRead();
400 i.state.replies = [];
401 i.state.mentions = [];
402 i.state.messages = [];
404 window.scrollTo(0, 0);
408 parseMessage(msg: WebSocketJsonResponse) {
410 let res = wsJsonToRes(msg);
412 toast(i18n.t(msg.error), 'danger');
414 } else if (msg.reconnect) {
416 } else if (res.op == UserOperation.GetReplies) {
417 let data = res.data as GetRepliesResponse;
418 this.state.replies = data.replies;
419 this.sendUnreadCount();
420 window.scrollTo(0, 0);
421 this.setState(this.state);
423 } else if (res.op == UserOperation.GetUserMentions) {
424 let data = res.data as GetUserMentionsResponse;
425 this.state.mentions = data.mentions;
426 this.sendUnreadCount();
427 window.scrollTo(0, 0);
428 this.setState(this.state);
430 } else if (res.op == UserOperation.GetPrivateMessages) {
431 let data = res.data as PrivateMessagesResponse;
432 this.state.messages = data.messages;
433 this.sendUnreadCount();
434 window.scrollTo(0, 0);
435 this.setState(this.state);
437 } else if (res.op == UserOperation.EditPrivateMessage) {
438 let data = res.data as PrivateMessageResponse;
439 let found: PrivateMessageI = this.state.messages.find(
440 m => m.id === data.message.id
442 found.content = data.message.content;
443 found.updated = data.message.updated;
444 found.deleted = data.message.deleted;
445 // If youre in the unread view, just remove it from the list
446 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) {
447 this.state.messages = this.state.messages.filter(
448 r => r.id !== data.message.id
451 let found = this.state.messages.find(c => c.id == data.message.id);
452 found.read = data.message.read;
454 this.sendUnreadCount();
455 window.scrollTo(0, 0);
456 this.setState(this.state);
458 } else if (res.op == UserOperation.MarkAllAsRead) {
459 // Moved to be instant
460 } else if (res.op == UserOperation.EditComment) {
461 let data = res.data as CommentResponse;
462 editCommentRes(data, this.state.replies);
464 // If youre in the unread view, just remove it from the list
465 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.comment.read) {
466 this.state.replies = this.state.replies.filter(
467 r => r.id !== data.comment.id
470 let found = this.state.replies.find(c => c.id == data.comment.id);
471 found.read = data.comment.read;
473 this.sendUnreadCount();
474 this.setState(this.state);
476 } else if (res.op == UserOperation.EditUserMention) {
477 let data = res.data as UserMentionResponse;
479 let found = this.state.mentions.find(c => c.id == data.mention.id);
480 found.content = data.mention.content;
481 found.updated = data.mention.updated;
482 found.removed = data.mention.removed;
483 found.deleted = data.mention.deleted;
484 found.upvotes = data.mention.upvotes;
485 found.downvotes = data.mention.downvotes;
486 found.score = data.mention.score;
488 // If youre in the unread view, just remove it from the list
489 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.mention.read) {
490 this.state.mentions = this.state.mentions.filter(
491 r => r.id !== data.mention.id
494 let found = this.state.mentions.find(c => c.id == data.mention.id);
495 found.read = data.mention.read;
497 this.sendUnreadCount();
498 this.setState(this.state);
499 } else if (res.op == UserOperation.CreateComment) {
500 let data = res.data as CommentResponse;
502 if (data.recipient_ids.includes(UserService.Instance.user.id)) {
503 this.state.replies.unshift(data.comment);
504 this.setState(this.state);
505 } else if (data.comment.creator_id == UserService.Instance.user.id) {
506 toast(i18n.t('reply_sent'));
508 this.setState(this.state);
509 } else if (res.op == UserOperation.CreatePrivateMessage) {
510 let data = res.data as PrivateMessageResponse;
511 if (data.message.recipient_id == UserService.Instance.user.id) {
512 this.state.messages.unshift(data.message);
513 this.setState(this.state);
515 } else if (res.op == UserOperation.SaveComment) {
516 let data = res.data as CommentResponse;
517 saveCommentRes(data, this.state.replies);
518 this.setState(this.state);
520 } else if (res.op == UserOperation.CreateCommentLike) {
521 let data = res.data as CommentResponse;
522 createCommentLikeRes(data, this.state.replies);
523 this.setState(this.state);
529 this.state.replies.filter(r => !r.read).length +
530 this.state.mentions.filter(r => !r.read).length +
531 this.state.messages.filter(
532 r => !r.read && r.creator_id !== UserService.Instance.user.id
534 UserService.Instance.user.unreadCount = count;
535 UserService.Instance.sub.next({
536 user: UserService.Instance.user,