1 import { Component, linkEvent } from 'inferno';
2 import { Subscription } from 'rxjs';
10 GetUserMentionsResponse,
13 WebSocketJsonResponse,
14 PrivateMessage as PrivateMessageI,
15 GetPrivateMessagesForm,
16 PrivateMessagesResponse,
17 PrivateMessageResponse,
19 } from 'lemmy-js-client';
20 import { WebSocketService, UserService } from '../services';
36 import { CommentNodes } from './comment-nodes';
37 import { PrivateMessage } from './private-message';
38 import { HtmlTags } from './html-tags';
39 import { SortSelect } from './sort-select';
40 import { i18n } from '../i18next';
41 import { InitialFetchRequest } from 'shared/interfaces';
55 type ReplyType = Comment | PrivateMessageI;
57 interface InboxState {
58 unreadOrAll: UnreadOrAll;
59 messageType: MessageType;
62 messages: PrivateMessageI[];
69 export class Inbox extends Component<any, InboxState> {
70 private isoData = setIsoData(this.context);
71 private subscription: Subscription;
72 private emptyState: InboxState = {
73 unreadOrAll: UnreadOrAll.Unread,
74 messageType: MessageType.All,
80 site: this.isoData.site.site,
84 constructor(props: any, context: any) {
85 super(props, context);
87 this.state = this.emptyState;
88 this.handleSortChange = this.handleSortChange.bind(this);
90 if (!UserService.Instance.user && isBrowser()) {
91 toast(i18n.t('not_logged_in'), 'danger');
92 this.context.router.history.push(`/login`);
95 this.parseMessage = this.parseMessage.bind(this);
96 this.subscription = wsSubscribe(this.parseMessage);
98 // Only fetch the data if coming from another route
99 if (this.isoData.path == this.context.router.route.match.url) {
100 this.state.replies = this.isoData.routeData[0].replies;
101 this.state.mentions = this.isoData.routeData[1].mentions;
102 this.state.messages = this.isoData.routeData[2].messages;
103 this.sendUnreadCount();
104 this.state.loading = false;
110 componentWillUnmount() {
112 this.subscription.unsubscribe();
116 get documentTitle(): string {
117 return `@${UserService.Instance.user.name} ${i18n.t('inbox')} - ${
124 <div class="container">
125 {this.state.loading ? (
127 <svg class="icon icon-spinner spin">
128 <use xlinkHref="#icon-spinner"></use>
135 title={this.documentTitle}
136 path={this.context.router.route.match.url}
142 href={`/feeds/inbox/${UserService.Instance.auth}.xml`}
147 <svg class="icon ml-2 text-muted small">
148 <use xlinkHref="#icon-rss">#</use>
153 {this.state.replies.length +
154 this.state.mentions.length +
155 this.state.messages.length >
157 this.state.unreadOrAll == UnreadOrAll.Unread && (
158 <ul class="list-inline mb-1 text-muted small font-weight-bold">
159 <li className="list-inline-item">
162 onClick={linkEvent(this, this.markAllAsRead)}
164 {i18n.t('mark_all_as_read')}
170 {this.state.messageType == MessageType.All && this.all()}
171 {this.state.messageType == MessageType.Replies && this.replies()}
172 {this.state.messageType == MessageType.Mentions &&
174 {this.state.messageType == MessageType.Messages &&
184 unreadOrAllRadios() {
186 <div class="btn-group btn-group-toggle flex-wrap mb-2">
188 className={`btn btn-outline-secondary pointer
189 ${this.state.unreadOrAll == UnreadOrAll.Unread && 'active'}
194 value={UnreadOrAll.Unread}
195 checked={this.state.unreadOrAll == UnreadOrAll.Unread}
196 onChange={linkEvent(this, this.handleUnreadOrAllChange)}
201 className={`btn btn-outline-secondary pointer
202 ${this.state.unreadOrAll == UnreadOrAll.All && 'active'}
207 value={UnreadOrAll.All}
208 checked={this.state.unreadOrAll == UnreadOrAll.All}
209 onChange={linkEvent(this, this.handleUnreadOrAllChange)}
217 messageTypeRadios() {
219 <div class="btn-group btn-group-toggle flex-wrap mb-2">
221 className={`btn btn-outline-secondary pointer
222 ${this.state.messageType == MessageType.All && 'active'}
227 value={MessageType.All}
228 checked={this.state.messageType == MessageType.All}
229 onChange={linkEvent(this, this.handleMessageTypeChange)}
234 className={`btn btn-outline-secondary pointer
235 ${this.state.messageType == MessageType.Replies && 'active'}
240 value={MessageType.Replies}
241 checked={this.state.messageType == MessageType.Replies}
242 onChange={linkEvent(this, this.handleMessageTypeChange)}
247 className={`btn btn-outline-secondary pointer
248 ${this.state.messageType == MessageType.Mentions && 'active'}
253 value={MessageType.Mentions}
254 checked={this.state.messageType == MessageType.Mentions}
255 onChange={linkEvent(this, this.handleMessageTypeChange)}
260 className={`btn btn-outline-secondary pointer
261 ${this.state.messageType == MessageType.Messages && 'active'}
266 value={MessageType.Messages}
267 checked={this.state.messageType == MessageType.Messages}
268 onChange={linkEvent(this, this.handleMessageTypeChange)}
278 <div className="mb-2">
279 <span class="mr-3">{this.unreadOrAllRadios()}</span>
280 <span class="mr-3">{this.messageTypeRadios()}</span>
282 sort={this.state.sort}
283 onChange={this.handleSortChange}
290 combined(): ReplyType[] {
292 ...this.state.replies,
293 ...this.state.mentions,
294 ...this.state.messages,
295 ].sort((a, b) => b.published.localeCompare(a.published));
301 {this.combined().map(i =>
305 nodes={[{ comment: i }]}
310 enableDownvotes={this.state.site.enable_downvotes}
313 <PrivateMessage key={i.id} privateMessage={i} />
324 nodes={commentsToFlatNodes(this.state.replies)}
329 enableDownvotes={this.state.site.enable_downvotes}
338 {this.state.mentions.map(mention => (
341 nodes={[{ comment: mention }]}
346 enableDownvotes={this.state.site.enable_downvotes}
356 {this.state.messages.map(message => (
357 <PrivateMessage key={message.id} privateMessage={message} />
366 {this.state.page > 1 && (
368 class="btn btn-secondary mr-1"
369 onClick={linkEvent(this, this.prevPage)}
374 {this.unreadCount() > 0 && (
376 class="btn btn-secondary"
377 onClick={linkEvent(this, this.nextPage)}
398 handleUnreadOrAllChange(i: Inbox, event: any) {
399 i.state.unreadOrAll = Number(event.target.value);
405 handleMessageTypeChange(i: Inbox, event: any) {
406 i.state.messageType = Number(event.target.value);
412 static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
413 let promises: Promise<any>[] = [];
415 // It can be /u/me, or /username/1
416 let repliesForm: GetRepliesForm = {
422 setAuth(repliesForm, req.auth);
423 promises.push(req.client.getReplies(repliesForm));
425 let userMentionsForm: GetUserMentionsForm = {
431 setAuth(userMentionsForm, req.auth);
432 promises.push(req.client.getUserMentions(userMentionsForm));
434 let privateMessagesForm: GetPrivateMessagesForm = {
439 setAuth(privateMessagesForm, req.auth);
440 promises.push(req.client.getPrivateMessages(privateMessagesForm));
446 let repliesForm: GetRepliesForm = {
447 sort: this.state.sort,
448 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
449 page: this.state.page,
452 WebSocketService.Instance.getReplies(repliesForm);
454 let userMentionsForm: GetUserMentionsForm = {
455 sort: this.state.sort,
456 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
457 page: this.state.page,
460 WebSocketService.Instance.getUserMentions(userMentionsForm);
462 let privateMessagesForm: GetPrivateMessagesForm = {
463 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
464 page: this.state.page,
467 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
470 handleSortChange(val: SortType) {
471 this.state.sort = val;
473 this.setState(this.state);
477 markAllAsRead(i: Inbox) {
478 WebSocketService.Instance.markAllAsRead();
479 i.state.replies = [];
480 i.state.mentions = [];
481 i.state.messages = [];
483 window.scrollTo(0, 0);
487 parseMessage(msg: WebSocketJsonResponse) {
489 let res = wsJsonToRes(msg);
491 toast(i18n.t(msg.error), 'danger');
493 } else if (msg.reconnect) {
495 } else if (res.op == UserOperation.GetReplies) {
496 let data = res.data as GetRepliesResponse;
497 this.state.replies = data.replies;
498 this.state.loading = false;
499 this.sendUnreadCount();
500 window.scrollTo(0, 0);
501 this.setState(this.state);
503 } else if (res.op == UserOperation.GetUserMentions) {
504 let data = res.data as GetUserMentionsResponse;
505 this.state.mentions = data.mentions;
506 this.sendUnreadCount();
507 window.scrollTo(0, 0);
508 this.setState(this.state);
510 } else if (res.op == UserOperation.GetPrivateMessages) {
511 let data = res.data as PrivateMessagesResponse;
512 this.state.messages = data.messages;
513 this.sendUnreadCount();
514 window.scrollTo(0, 0);
515 this.setState(this.state);
517 } else if (res.op == UserOperation.EditPrivateMessage) {
518 let data = res.data as PrivateMessageResponse;
519 let found: PrivateMessageI = this.state.messages.find(
520 m => m.id === data.message.id
523 found.content = data.message.content;
524 found.updated = data.message.updated;
526 this.setState(this.state);
527 } else if (res.op == UserOperation.DeletePrivateMessage) {
528 let data = res.data as PrivateMessageResponse;
529 let found: PrivateMessageI = this.state.messages.find(
530 m => m.id === data.message.id
533 found.deleted = data.message.deleted;
534 found.updated = data.message.updated;
536 this.setState(this.state);
537 } else if (res.op == UserOperation.MarkPrivateMessageAsRead) {
538 let data = res.data as PrivateMessageResponse;
539 let found: PrivateMessageI = this.state.messages.find(
540 m => m.id === data.message.id
544 found.updated = data.message.updated;
546 // If youre in the unread view, just remove it from the list
547 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) {
548 this.state.messages = this.state.messages.filter(
549 r => r.id !== data.message.id
552 let found = this.state.messages.find(c => c.id == data.message.id);
553 found.read = data.message.read;
556 this.sendUnreadCount();
557 this.setState(this.state);
558 } else if (res.op == UserOperation.MarkAllAsRead) {
559 // Moved to be instant
561 res.op == UserOperation.EditComment ||
562 res.op == UserOperation.DeleteComment ||
563 res.op == UserOperation.RemoveComment
565 let data = res.data as CommentResponse;
566 editCommentRes(data, this.state.replies);
567 this.setState(this.state);
568 } else if (res.op == UserOperation.MarkCommentAsRead) {
569 let data = res.data as CommentResponse;
571 // If youre in the unread view, just remove it from the list
572 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.comment.read) {
573 this.state.replies = this.state.replies.filter(
574 r => r.id !== data.comment.id
577 let found = this.state.replies.find(c => c.id == data.comment.id);
578 found.read = data.comment.read;
580 this.sendUnreadCount();
581 this.setState(this.state);
583 } else if (res.op == UserOperation.MarkUserMentionAsRead) {
584 let data = res.data as UserMentionResponse;
586 let found = this.state.mentions.find(c => c.id == data.mention.id);
587 found.content = data.mention.content;
588 found.updated = data.mention.updated;
589 found.removed = data.mention.removed;
590 found.deleted = data.mention.deleted;
591 found.upvotes = data.mention.upvotes;
592 found.downvotes = data.mention.downvotes;
593 found.score = data.mention.score;
595 // If youre in the unread view, just remove it from the list
596 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.mention.read) {
597 this.state.mentions = this.state.mentions.filter(
598 r => r.id !== data.mention.id
601 let found = this.state.mentions.find(c => c.id == data.mention.id);
602 found.read = data.mention.read;
604 this.sendUnreadCount();
605 this.setState(this.state);
606 } else if (res.op == UserOperation.CreateComment) {
607 let data = res.data as CommentResponse;
609 if (data.recipient_ids.includes(UserService.Instance.user.id)) {
610 this.state.replies.unshift(data.comment);
611 this.setState(this.state);
612 } else if (data.comment.creator_id == UserService.Instance.user.id) {
613 toast(i18n.t('reply_sent'));
615 } else if (res.op == UserOperation.CreatePrivateMessage) {
616 let data = res.data as PrivateMessageResponse;
617 if (data.message.recipient_id == UserService.Instance.user.id) {
618 this.state.messages.unshift(data.message);
619 this.setState(this.state);
621 } else if (res.op == UserOperation.SaveComment) {
622 let data = res.data as CommentResponse;
623 saveCommentRes(data, this.state.replies);
624 this.setState(this.state);
626 } else if (res.op == UserOperation.CreateCommentLike) {
627 let data = res.data as CommentResponse;
628 createCommentLikeRes(data, this.state.replies);
629 this.setState(this.state);
634 UserService.Instance.unreadCountSub.next(this.unreadCount());
637 unreadCount(): number {
639 this.state.replies.filter(r => !r.read).length +
640 this.state.mentions.filter(r => !r.read).length +
641 this.state.messages.filter(
643 UserService.Instance.user &&
645 r.creator_id !== UserService.Instance.user.id